{"version":3,"file":"middleware.mjs","names":["retryDelaySeconds","retryDelaySeconds","resolveExclusiveHooksShared","virtualPlugins","virtualCreateDialect","virtualCreateCoalescingDialect","virtualCreateStorage","virtualCreateScheduler","virtualSandboxedPlugins","virtualMediaProviders","createRequestScopedDb","virtualCreateRequestScopedDb","virtualBuildTime"],"sources":["../../src/i18n/repair-locale-casing.ts","../../src/i18n/taxonomy-locale-diagnostic.ts","../../src/media/usage/collection-deletion-processor.ts","../../src/media/usage/reconciliation.ts","../../src/media/usage/reconciliation-processor.ts","../../src/media/usage/work-processor.ts","../../src/plugins/sandbox/runner-options.ts","../../src/media/usage/cleanup.ts","../../src/cleanup.ts","../../src/comments/moderator.ts","../../src/scheduled-publish.ts","../../src/emdash-runtime.ts","../../src/media/url.ts","../../src/astro/middleware/scoped-db.ts","../../src/astro/middleware/stream-end-metrics.ts","../../src/astro/prefetch.ts","../../src/astro/public-plugin-api-routes.ts","../../src/astro/middleware.ts"],"sourcesContent":["import type { Kysely } from \"kysely\";\nimport { sql } from \"kysely\";\n\nimport { listTablesLike } from \"../database/dialect-helpers.js\";\nimport type { Database } from \"../database/types.js\";\n\n/** Rewrite stored locales to the exact casing used by the site configuration. */\nexport async function repairLocaleCasing(\n\tdb: Kysely<Database>,\n\tconfiguredLocales: readonly string[],\n): Promise<void> {\n\tconst tableNames = await listTablesLike(db, \"ec_%\");\n\n\tfor (const tableName of tableNames) {\n\t\tconst table = sql.ref(tableName);\n\t\tfor (const locale of configuredLocales) {\n\t\t\tawait sql`\n\t\t\t\tUPDATE ${table} AS target\n\t\t\t\tSET locale = ${locale}\n\t\t\t\tWHERE lower(target.locale) = lower(${locale})\n\t\t\t\t\tAND target.locale != ${locale}\n\t\t\t\t\tAND NOT EXISTS (\n\t\t\t\t\t\tSELECT 1\n\t\t\t\t\t\tFROM ${table} AS existing\n\t\t\t\t\t\tWHERE existing.slug = target.slug AND existing.locale = ${locale}\n\t\t\t\t\t)\n\t\t\t\t\tAND target.id = (\n\t\t\t\t\t\tSELECT MIN(candidate.id)\n\t\t\t\t\t\tFROM ${table} AS candidate\n\t\t\t\t\t\tWHERE candidate.slug = target.slug\n\t\t\t\t\t\t\tAND lower(candidate.locale) = lower(${locale})\n\t\t\t\t\t\t\tAND candidate.locale != ${locale}\n\t\t\t\t\t)\n\t\t\t`.execute(db);\n\t\t}\n\t}\n}\n","import type { Kysely } from \"kysely\";\n\nimport type { Database } from \"../database/types.js\";\n\nconst REPAIR_GUIDE =\n\t\"https://docs.emdashcms.com/guides/internationalization/#repairing-taxonomy-locale-mismatches\";\n\ninterface TaxonomyLocaleMismatch {\n\tsource: \"definitions\" | \"terms\";\n\tlocale: string;\n}\n\nexport async function warnAboutUnconfiguredTaxonomyLocales(\n\tdb: Kysely<Database>,\n\tconfiguredLocales: readonly string[],\n\tdefinitionLocales?: readonly string[],\n): Promise<void> {\n\tconst supportedLocales = configuredLocales.length > 0 ? configuredLocales : [\"en\"];\n\tconst definitionRows =\n\t\tdefinitionLocales === undefined\n\t\t\t? await db\n\t\t\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t\t\t.select(\"locale\")\n\t\t\t\t\t.distinct()\n\t\t\t\t\t.where(\"locale\", \"not in\", supportedLocales)\n\t\t\t\t\t.execute()\n\t\t\t: [...new Set(definitionLocales)]\n\t\t\t\t\t.filter((locale) => !supportedLocales.includes(locale))\n\t\t\t\t\t.map((locale) => ({ locale }));\n\tconst termRows = await db\n\t\t.selectFrom(\"taxonomies\")\n\t\t.select(\"locale\")\n\t\t.distinct()\n\t\t.where(\"locale\", \"not in\", supportedLocales)\n\t\t.execute();\n\tconst mismatches: TaxonomyLocaleMismatch[] = [\n\t\t...definitionRows.map(({ locale }) => ({ source: \"definitions\" as const, locale })),\n\t\t...termRows.map(({ locale }) => ({ source: \"terms\" as const, locale })),\n\t].toSorted((a, b) => a.source.localeCompare(b.source) || a.locale.localeCompare(b.locale));\n\tif (mismatches.length === 0) return;\n\n\tconst details = mismatches.map(({ source, locale }) => `${source}: ${locale}`).join(\"; \");\n\tconsole.warn(\n\t\t`EmDash: Taxonomy rows use locales outside the configured locales (${supportedLocales.join(\", \")}): ${details}. ` +\n\t\t\t`Locale-scoped reads may not return these rows. Review and repair them explicitly: ${REPAIR_GUIDE}`,\n\t);\n}\n","import { sql, type Kysely, type RawBuilder, type Transaction, type Updateable } from \"kysely\";\n\nimport { isPostgres, tableExists } from \"../../database/dialect-helpers.js\";\nimport { withTransaction } from \"../../database/transaction.js\";\nimport type { Database, MediaUsageCollectionDeletionTable } from \"../../database/types.js\";\nimport {\n\tcollectionDeletionCurrentTimestamp,\n\tdeleteActivatedMediaUsageCollection,\n\tMediaUsageCollectionDeletionRepository,\n\ttype MediaUsageCollectionDeletionRecord,\n} from \"./collection-deletion.js\";\n\nexport const MEDIA_USAGE_COLLECTION_DELETION_LIMITS = Object.freeze({\n\tcandidatesPerTick: 4,\n\tdeletionsPerTick: 1,\n\trowsPerBatch: 50,\n\tleaseDurationSeconds: 5 * 60,\n\tmaxAttempts: 5,\n\tretryBaseSeconds: 30,\n\tretryMaxSeconds: 15 * 60,\n\tmaxQueriesPerTick: 30,\n});\n\nexport interface MediaUsageCollectionDeletionTickResult {\n\tcandidateCount: number;\n\tclaimedCount: number;\n\toutcome: \"idle\" | \"progress\" | \"finalized\" | \"retry\" | \"failed\" | \"claim_lost\";\n}\n\ntype DatabaseExecutor = Kysely<Database> | Transaction<Database>;\n\nexport async function processDueMediaUsageCollectionDeletions(\n\tdb: Kysely<Database>,\n): Promise<MediaUsageCollectionDeletionTickResult> {\n\tconst repository = new MediaUsageCollectionDeletionRepository(db);\n\tconst candidates = await repository.findDue(\n\t\tMEDIA_USAGE_COLLECTION_DELETION_LIMITS.candidatesPerTick,\n\t);\n\tif (candidates.length === 0) return { candidateCount: 0, claimedCount: 0, outcome: \"idle\" };\n\n\tlet claim: (MediaUsageCollectionDeletionRecord & { leaseToken: string }) | null = null;\n\tfor (const candidate of candidates) {\n\t\tclaim = await repository.claim({\n\t\t\tcollectionId: candidate.collectionId,\n\t\t\tphase: candidate.phase,\n\t\t\tleaseDurationSeconds: MEDIA_USAGE_COLLECTION_DELETION_LIMITS.leaseDurationSeconds,\n\t\t});\n\t\tif (claim?.leaseToken) break;\n\t}\n\tif (!claim?.leaseToken) {\n\t\treturn { candidateCount: candidates.length, claimedCount: 0, outcome: \"claim_lost\" };\n\t}\n\n\ttry {\n\t\tconst processed = await processClaimedDeletion(db, claim);\n\t\tif (!processed.finalized && !processed.released && !(await repository.release(claim))) {\n\t\t\treturn { candidateCount: candidates.length, claimedCount: 1, outcome: \"claim_lost\" };\n\t\t}\n\t\treturn {\n\t\t\tcandidateCount: candidates.length,\n\t\t\tclaimedCount: 1,\n\t\t\toutcome: processed.finalized ? \"finalized\" : \"progress\",\n\t\t};\n\t} catch (error) {\n\t\tconst terminal = claim.attemptCount + 1 >= MEDIA_USAGE_COLLECTION_DELETION_LIMITS.maxAttempts;\n\t\tconst recorded = await repository.recordFailure({\n\t\t\tcollectionId: claim.collectionId,\n\t\t\tleaseToken: claim.leaseToken,\n\t\t\terrorCode: \"MEDIA_USAGE_COLLECTION_DELETION_FAILED\",\n\t\t\tterminal,\n\t\t\tretryDelaySeconds: retryDelaySeconds(claim.attemptCount),\n\t\t});\n\t\tif (!recorded) {\n\t\t\treturn { candidateCount: candidates.length, claimedCount: 1, outcome: \"claim_lost\" };\n\t\t}\n\t\tconsole.error(\"[media-usage:collection-deletion] Processing failed:\", error);\n\t\treturn {\n\t\t\tcandidateCount: candidates.length,\n\t\t\tclaimedCount: 1,\n\t\t\toutcome: terminal ? \"failed\" : \"retry\",\n\t\t};\n\t}\n}\n\nasync function processClaimedDeletion(\n\tdb: Kysely<Database>,\n\tclaim: MediaUsageCollectionDeletionRecord & { leaseToken: string },\n): Promise<{ finalized: boolean; released: boolean }> {\n\tif (claim.phase === \"fence\" || claim.phase === \"registry\" || claim.phase === \"table\") {\n\t\tawait deleteActivatedMediaUsageCollection(\n\t\t\tdb,\n\t\t\t{\n\t\t\t\tcollectionId: claim.collectionId,\n\t\t\t\tcollectionSlug: claim.collectionSlug,\n\t\t\t\tforceDelete: claim.forceDelete,\n\t\t\t},\n\t\t\t{ frontPhaseLimit: 1, claimed: claim },\n\t\t);\n\t\treturn { finalized: false, released: true };\n\t}\n\tif (claim.phase === \"work\") await processWorkBatch(db, claim);\n\tif (claim.phase === \"sources\") await processSourceBatch(db, claim);\n\tif (claim.phase === \"status\") await processStatus(db, claim);\n\tif (claim.phase === \"finalize\") {\n\t\tawait finalizeDeletion(db, claim);\n\t\treturn { finalized: true, released: true };\n\t}\n\treturn { finalized: false, released: false };\n}\n\nasync function processWorkBatch(\n\tdb: Kysely<Database>,\n\tclaim: MediaUsageCollectionDeletionRecord & { leaseToken: string },\n): Promise<boolean> {\n\tawait withTransaction(db, async (trx) => {\n\t\tconst rows = await trx\n\t\t\t.selectFrom(\"_emdash_media_usage_work\")\n\t\t\t.select(\"content_id\")\n\t\t\t.where(\"collection_id\", \"=\", claim.collectionId)\n\t\t\t.$if(claim.workCursor !== null, (query) => query.where(\"content_id\", \">\", claim.workCursor!))\n\t\t\t.orderBy(\"content_id\", \"asc\")\n\t\t\t.limit(MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch + 1)\n\t\t\t.execute();\n\t\tconst batch = rows.slice(0, MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch);\n\t\tif (batch.length > 0) {\n\t\t\tawait trx\n\t\t\t\t.deleteFrom(\"_emdash_media_usage_work\")\n\t\t\t\t.where(\"collection_id\", \"=\", claim.collectionId)\n\t\t\t\t.where(\n\t\t\t\t\t\"content_id\",\n\t\t\t\t\t\"in\",\n\t\t\t\t\tbatch.map((row) => row.content_id),\n\t\t\t\t)\n\t\t\t\t.where(liveLeaseGuard(trx, claim))\n\t\t\t\t.execute();\n\t\t}\n\t\tawait updateDeletion(trx, claim, {\n\t\t\tphase: rows.length > MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch ? \"work\" : \"sources\",\n\t\t\twork_cursor:\n\t\t\t\trows.length > MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch\n\t\t\t\t\t? batch.at(-1)!.content_id\n\t\t\t\t\t: null,\n\t\t});\n\t});\n\treturn false;\n}\n\nasync function processSourceBatch(\n\tdb: Kysely<Database>,\n\tclaim: MediaUsageCollectionDeletionRecord & { leaseToken: string },\n): Promise<boolean> {\n\tawait withTransaction(db, async (trx) => {\n\t\tlet sourceKey = claim.sourceKey;\n\t\tif (!sourceKey) {\n\t\t\tconst source = await trx\n\t\t\t\t.selectFrom(\"_emdash_media_usage_sources\")\n\t\t\t\t.select(\"source_key\")\n\t\t\t\t.where(\"source_type\", \"=\", \"content\")\n\t\t\t\t.where(\"collection_id\", \"=\", claim.collectionId)\n\t\t\t\t.orderBy(\"source_key\", \"asc\")\n\t\t\t\t.limit(1)\n\t\t\t\t.executeTakeFirst();\n\t\t\tif (!source) {\n\t\t\t\tawait updateDeletion(trx, claim, {\n\t\t\t\t\tphase: \"status\",\n\t\t\t\t\tsource_key: null,\n\t\t\t\t\toccurrence_cursor: null,\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tsourceKey = source.source_key;\n\t\t\tawait updateDeletion(trx, claim, { source_key: sourceKey, occurrence_cursor: null });\n\t\t}\n\n\t\tconst occurrences = await trx\n\t\t\t.selectFrom(\"_emdash_media_usage\")\n\t\t\t.select(\"id\")\n\t\t\t.where(\"source_key\", \"=\", sourceKey)\n\t\t\t.$if(claim.occurrenceCursor !== null, (query) =>\n\t\t\t\tquery.where(\"id\", \">\", claim.occurrenceCursor!),\n\t\t\t)\n\t\t\t.orderBy(\"id\", \"asc\")\n\t\t\t.limit(MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch + 1)\n\t\t\t.execute();\n\t\tconst batch = occurrences.slice(0, MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch);\n\t\tif (batch.length > 0) {\n\t\t\tawait trx\n\t\t\t\t.deleteFrom(\"_emdash_media_usage\")\n\t\t\t\t.where(\"source_key\", \"=\", sourceKey)\n\t\t\t\t.where(\n\t\t\t\t\t\"id\",\n\t\t\t\t\t\"in\",\n\t\t\t\t\tbatch.map((row) => row.id),\n\t\t\t\t)\n\t\t\t\t.where(liveLeaseGuard(trx, claim))\n\t\t\t\t.execute();\n\t\t}\n\t\tif (occurrences.length > MEDIA_USAGE_COLLECTION_DELETION_LIMITS.rowsPerBatch) {\n\t\t\tawait updateDeletion(trx, claim, {\n\t\t\t\tsource_key: sourceKey,\n\t\t\t\toccurrence_cursor: batch.at(-1)!.id,\n\t\t\t});\n\t\t\treturn;\n\t\t}\n\t\tawait trx\n\t\t\t.deleteFrom(\"_emdash_media_usage_sources\")\n\t\t\t.where(\"source_key\", \"=\", sourceKey)\n\t\t\t.where(\"source_type\", \"=\", \"content\")\n\t\t\t.where(\"collection_id\", \"=\", claim.collectionId)\n\t\t\t.where(liveLeaseGuard(trx, claim))\n\t\t\t.execute();\n\t\tawait updateDeletion(trx, claim, { source_key: null, occurrence_cursor: null });\n\t});\n\treturn false;\n}\n\nasync function processStatus(\n\tdb: Kysely<Database>,\n\tclaim: MediaUsageCollectionDeletionRecord & { leaseToken: string },\n): Promise<boolean> {\n\tawait withTransaction(db, async (trx) => {\n\t\tif (await exactCleanupRowsRemain(trx, claim, false)) {\n\t\t\tthrow new Error(\"Collection deletion cleanup is incomplete\");\n\t\t}\n\t\tawait trx\n\t\t\t.deleteFrom(\"_emdash_media_usage_reconciliations\")\n\t\t\t.where(\"collection_id\", \"=\", claim.collectionId)\n\t\t\t.where(\"collection_slug\", \"=\", claim.collectionSlug)\n\t\t\t.where(liveLeaseGuard(trx, claim))\n\t\t\t.execute();\n\t\tawait trx\n\t\t\t.deleteFrom(\"_emdash_media_usage_index_status\")\n\t\t\t.where(\"adapter_id\", \"=\", \"content-media\")\n\t\t\t.where(\"scope_type\", \"=\", \"collection\")\n\t\t\t.where(\"scope_key\", \"=\", claim.collectionSlug)\n\t\t\t.where(\"collection_id\", \"=\", claim.collectionId)\n\t\t\t.where(liveLeaseGuard(trx, claim))\n\t\t\t.execute();\n\t\tawait updateDeletion(trx, claim, { phase: \"finalize\" });\n\t});\n\treturn false;\n}\n\nasync function finalizeDeletion(\n\tdb: Kysely<Database>,\n\tclaim: MediaUsageCollectionDeletionRecord & { leaseToken: string },\n): Promise<boolean> {\n\tif (await tableExists(db, `ec_${claim.collectionSlug}`)) {\n\t\tthrow new Error(\"Collection table still exists during deletion finalization\");\n\t}\n\tif (await exactCleanupRowsRemain(db, claim)) throw new Error(\"Collection deletion is incomplete\");\n\tconst registry = await db\n\t\t.selectFrom(\"_emdash_collections\")\n\t\t.select(\"id\")\n\t\t.where(\"id\", \"=\", claim.collectionId)\n\t\t.where(\"slug\", \"=\", claim.collectionSlug)\n\t\t.executeTakeFirst();\n\tif (registry) throw new Error(\"Collection registry identity still exists\");\n\tconst result = await db\n\t\t.deleteFrom(\"_emdash_media_usage_collection_deletions\")\n\t\t.where(\"collection_id\", \"=\", claim.collectionId)\n\t\t.where(\"collection_slug\", \"=\", claim.collectionSlug)\n\t\t.where(\"state\", \"=\", \"leased\")\n\t\t.where(\"phase\", \"=\", \"finalize\")\n\t\t.where(\"lease_token\", \"=\", claim.leaseToken)\n\t\t.where(liveLeaseGuard(db, claim))\n\t\t.executeTakeFirst();\n\tif (Number(result.numDeletedRows ?? 0) !== 1)\n\t\tthrow new Error(\"Collection deletion lost finalization\");\n\treturn true;\n}\n\nasync function exactCleanupRowsRemain(\n\tdb: DatabaseExecutor,\n\tclaim: Pick<MediaUsageCollectionDeletionRecord, \"collectionId\" | \"collectionSlug\">,\n\tincludeStatus = true,\n): Promise<boolean> {\n\tconst result = await sql<{\n\t\twork_present: boolean | number;\n\t\tsource_present: boolean | number;\n\t\tstatus_present: boolean | number;\n\t}>`\n\t\tSELECT\n\t\t\tEXISTS (\n\t\t\t\tSELECT 1 FROM _emdash_media_usage_work\n\t\t\t\tWHERE collection_id = ${claim.collectionId}\n\t\t\t) AS work_present,\n\t\t\tEXISTS (\n\t\t\t\tSELECT 1 FROM _emdash_media_usage_sources\n\t\t\t\tWHERE source_type = 'content' AND collection_id = ${claim.collectionId}\n\t\t\t) AS source_present,\n\t\t\tEXISTS (\n\t\t\t\tSELECT 1 FROM _emdash_media_usage_index_status\n\t\t\t\tWHERE adapter_id = 'content-media'\n\t\t\t\t\tAND scope_type = 'collection'\n\t\t\t\t\tAND scope_key = ${claim.collectionSlug}\n\t\t\t\t\tAND collection_id = ${claim.collectionId}\n\t\t\t) AS status_present\n\t`.execute(db);\n\tconst row = result.rows[0];\n\treturn (\n\t\tBoolean(row?.work_present) ||\n\t\tBoolean(row?.source_present) ||\n\t\t(includeStatus && Boolean(row?.status_present))\n\t);\n}\n\nasync function updateDeletion(\n\tdb: DatabaseExecutor,\n\tclaim: MediaUsageCollectionDeletionRecord & { leaseToken: string },\n\tvalues: Updateable<MediaUsageCollectionDeletionTable>,\n): Promise<void> {\n\tconst result = await db\n\t\t.updateTable(\"_emdash_media_usage_collection_deletions\")\n\t\t.set({\n\t\t\t...values,\n\t\t\tattempt_count: 0,\n\t\t\tlast_error_code: null,\n\t\t\tupdated_at: collectionDeletionCurrentTimestamp(db),\n\t\t})\n\t\t.where(\"collection_id\", \"=\", claim.collectionId)\n\t\t.where(\"state\", \"=\", \"leased\")\n\t\t.where(\"lease_token\", \"=\", claim.leaseToken)\n\t\t.where(liveLeaseGuard(db, claim))\n\t\t.executeTakeFirst();\n\tif (Number(result.numUpdatedRows ?? 0) !== 1)\n\t\tthrow new Error(\"Collection deletion lease was lost\");\n}\n\nfunction liveLeaseGuard(\n\tdb: DatabaseExecutor,\n\tclaim: Pick<MediaUsageCollectionDeletionRecord, \"collectionId\"> & { leaseToken: string },\n): RawBuilder<boolean> {\n\treturn isPostgres(db)\n\t\t? sql<boolean>`EXISTS (\n\t\t\tSELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion\n\t\t\tWHERE deletion.collection_id = ${claim.collectionId}\n\t\t\t\tAND deletion.state = 'leased'\n\t\t\t\tAND deletion.lease_token = ${claim.leaseToken}\n\t\t\t\tAND deletion.lease_expires_at::timestamptz > clock_timestamp()\n\t\t)`\n\t\t: sql<boolean>`EXISTS (\n\t\t\tSELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion\n\t\t\tWHERE deletion.collection_id = ${claim.collectionId}\n\t\t\t\tAND deletion.state = 'leased'\n\t\t\t\tAND deletion.lease_token = ${claim.leaseToken}\n\t\t\t\tAND deletion.lease_expires_at > strftime('%Y-%m-%dT%H:%M:%fZ', 'now')\n\t\t)`;\n}\n\nfunction retryDelaySeconds(attemptCount: number): number {\n\treturn Math.min(\n\t\tMEDIA_USAGE_COLLECTION_DELETION_LIMITS.retryMaxSeconds,\n\t\tMEDIA_USAGE_COLLECTION_DELETION_LIMITS.retryBaseSeconds * 2 ** attemptCount,\n\t);\n}\n","import { sql, type Kysely, type RawBuilder, type Selectable } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport { isPostgres } from \"../../database/dialect-helpers.js\";\nimport type { Database, MediaUsageReconciliationTable } from \"../../database/types.js\";\nimport { validateIdentifier } from \"../../database/validate.js\";\n\nconst ACTIVATION_KEY = \"incremental_capture\";\nconst CONTENT_ADAPTER_ID = \"content-media\";\nconst COLLECTION_SCOPE = \"collection\";\nconst MAX_CANDIDATES = 100;\nconst MAX_PORTABLE_DURATION_SECONDS = 365 * 24 * 60 * 60;\nconst STABLE_ERROR_CODE_PATTERN = /^[A-Z][A-Z0-9_]{0,63}$/;\n\nexport type MediaUsageReconciliationState = \"pending\" | \"retry\" | \"leased\" | \"failed\";\nexport type MediaUsageReconciliationPhase = \"scan\" | \"sources\";\n\nexport interface MediaUsageReconciliationRecord {\n\tcollectionId: string;\n\tcollectionSlug: string;\n\trunToken: string;\n\ttargetEpoch: number | string | null;\n\tfieldFingerprint: string | null;\n\tstate: MediaUsageReconciliationState;\n\tphase: MediaUsageReconciliationPhase;\n\tscanCursor: string | null;\n\tscanUpperId: string | null;\n\tsourceCursor: string | null;\n\tsourceUpperKey: string | null;\n\tattemptCount: number;\n\tnextAttemptAt: string;\n\tleaseToken: string | null;\n\tleaseExpiresAt: string | null;\n\tlastErrorCode: string | null;\n\tcreatedAt: string;\n\tupdatedAt: string;\n}\n\nexport interface MediaUsageReconciliationClaim extends MediaUsageReconciliationRecord {\n\tleaseToken: string;\n}\n\nexport interface MediaUsageReconciliationSourceCandidate {\n\tsourceKey: string;\n\tcontentId: string | null;\n\tsourceVariant: string;\n}\n\nexport type MediaUsageReconciliationWorkBarrier =\n\t| { state: \"empty\" }\n\t| { state: \"pending\" }\n\t| { state: \"failed\"; errorCode: string };\n\nexport class MediaUsageReconciliationRepository {\n\tconstructor(private db: Kysely<Database>) {}\n\n\tasync findByIdentity(\n\t\tcollectionId: string,\n\t\trunToken: string,\n\t): Promise<MediaUsageReconciliationRecord | null> {\n\t\tassertIdentity({ collectionId, runToken });\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_emdash_media_usage_reconciliations\")\n\t\t\t.selectAll()\n\t\t\t.where(\"collection_id\", \"=\", collectionId)\n\t\t\t.where(\"run_token\", \"=\", runToken)\n\t\t\t.executeTakeFirst();\n\t\treturn row ? rowToRecord(row) : null;\n\t}\n\n\tasync beginRun(claim: MediaUsageReconciliationClaim): Promise<number | string | null> {\n\t\tconst now = timestampOffset(this.db, 0);\n\t\tconst sameRun = sql<boolean>`status = 'running' AND cursor = ${claim.runToken}`;\n\t\tconst row = await this.db\n\t\t\t.updateTable(\"_emdash_media_usage_index_status as status\")\n\t\t\t.set({\n\t\t\t\tstatus: \"running\",\n\t\t\t\tstarted_at: sql<string | null>`CASE WHEN ${sameRun} THEN started_at ELSE ${now} END`,\n\t\t\t\tcompleted_at: null,\n\t\t\t\tcursor: claim.runToken,\n\t\t\t\tindexed_source_count: 0,\n\t\t\t\tfailed_source_count: 0,\n\t\t\t\tlast_error_code: null,\n\t\t\t\tchange_epoch: sql<number>`CASE WHEN ${sameRun} THEN change_epoch ELSE change_epoch + 1 END`,\n\t\t\t\treconciliation_required: 1,\n\t\t\t\tupdated_at: now,\n\t\t\t})\n\t\t\t.where(\"status.adapter_id\", \"=\", CONTENT_ADAPTER_ID)\n\t\t\t.where(\"status.scope_type\", \"=\", COLLECTION_SCOPE)\n\t\t\t.where(\"status.collection_id\", \"=\", claim.collectionId)\n\t\t\t.where(\"status.scope_key\", \"=\", claim.collectionSlug)\n\t\t\t.where(\"status.capture_state\", \"=\", \"active\")\n\t\t\t.where(\"status.reconciliation_required\", \"=\", 1)\n\t\t\t.where((eb) =>\n\t\t\t\teb.or([eb(\"status.status\", \"!=\", \"running\"), eb(\"status.cursor\", \"=\", claim.runToken)]),\n\t\t\t)\n\t\t\t.where(this.liveClaimExists(claim))\n\t\t\t.returning(\"change_epoch\")\n\t\t\t.executeTakeFirst();\n\t\treturn row?.change_epoch ?? null;\n\t}\n\n\tasync findScanUpperId(claim: MediaUsageReconciliationClaim): Promise<string | null> {\n\t\tconst tableName = contentTableName(claim.collectionSlug);\n\t\tconst result = await sql<{ id: string }>`\n\t\t\tSELECT content.id\n\t\t\tFROM ${sql.ref(tableName)} AS content\n\t\t\tWHERE ${this.liveClaimExistsSql(claim)}\n\t\t\tORDER BY content.id DESC\n\t\t\tLIMIT 1\n\t\t`.execute(this.db);\n\t\treturn result.rows[0]?.id ?? null;\n\t}\n\n\tasync initializeScan(input: {\n\t\tclaim: MediaUsageReconciliationClaim;\n\t\ttargetEpoch: number | string;\n\t\tfieldFingerprint: string;\n\t\tscanUpperId: string | null;\n\t}): Promise<boolean> {\n\t\tconst result = await this.db\n\t\t\t.updateTable(\"_emdash_media_usage_reconciliations as reconciliation\")\n\t\t\t.set({\n\t\t\t\ttarget_epoch: input.targetEpoch,\n\t\t\t\tfield_fingerprint: input.fieldFingerprint,\n\t\t\t\tphase: \"scan\",\n\t\t\t\tscan_cursor: null,\n\t\t\t\tscan_upper_id: input.scanUpperId,\n\t\t\t\tsource_cursor: null,\n\t\t\t\tsource_upper_key: null,\n\t\t\t\tattempt_count: 0,\n\t\t\t\tlast_error_code: null,\n\t\t\t\tupdated_at: timestampOffset(this.db, 0),\n\t\t\t})\n\t\t\t.where(\"reconciliation.collection_id\", \"=\", input.claim.collectionId)\n\t\t\t.where(\"reconciliation.run_token\", \"=\", input.claim.runToken)\n\t\t\t.where(\"reconciliation.target_epoch\", \"is\", null)\n\t\t\t.where(\"reconciliation.state\", \"=\", \"leased\")\n\t\t\t.where(\"reconciliation.lease_token\", \"=\", input.claim.leaseToken)\n\t\t\t.where(liveLease(this.db))\n\t\t\t.where(this.statusOwnsRun(input.claim, input.targetEpoch))\n\t\t\t.executeTakeFirst();\n\t\treturn Number(result.numUpdatedRows ?? 0) === 1;\n\t}\n\n\tasync findScanPage(\n\t\treconciliation: MediaUsageReconciliationRecord,\n\t\tlimit: number,\n\t): Promise<string[]> {\n\t\tif (!Number.isSafeInteger(limit) || limit < 1 || limit > 50) {\n\t\t\tthrow new Error(\"Reconciliation scan page limit must be from 1 to 50\");\n\t\t}\n\t\tif (!reconciliation.leaseToken || reconciliation.targetEpoch === null) return [];\n\t\tconst tableName = contentTableName(reconciliation.collectionSlug);\n\t\tconst lowerBound = reconciliation.scanCursor\n\t\t\t? sql`AND content.id > ${reconciliation.scanCursor}`\n\t\t\t: sql``;\n\t\tconst upperBound = reconciliation.scanUpperId\n\t\t\t? sql`AND content.id <= ${reconciliation.scanUpperId}`\n\t\t\t: sql`AND 1 = 0`;\n\t\tconst result = await sql<{ id: string }>`\n\t\t\tSELECT content.id\n\t\t\tFROM ${sql.ref(tableName)} AS content\n\t\t\tWHERE 1 = 1\n\t\t\t\t${lowerBound}\n\t\t\t\t${upperBound}\n\t\t\t\tAND ${this.liveClaimExistsSql(reconciliation)}\n\t\t\tORDER BY content.id ASC\n\t\t\tLIMIT ${limit}\n\t\t`.execute(this.db);\n\t\treturn result.rows.map((row) => row.id);\n\t}\n\n\tasync checkpointScan(input: {\n\t\tclaim: MediaUsageReconciliationClaim;\n\t\ttargetEpoch: number | string;\n\t\tpreviousCursor: string | null;\n\t\tnextCursor: string;\n\t}): Promise<boolean> {\n\t\tlet query = this.db\n\t\t\t.updateTable(\"_emdash_media_usage_reconciliations as reconciliation\")\n\t\t\t.set({\n\t\t\t\tscan_cursor: input.nextCursor,\n\t\t\t\tattempt_count: 0,\n\t\t\t\tlast_error_code: null,\n\t\t\t\tupdated_at: timestampOffset(this.db, 0),\n\t\t\t})\n\t\t\t.where(\"reconciliation.collection_id\", \"=\", input.claim.collectionId)\n\t\t\t.where(\"reconciliation.run_token\", \"=\", input.claim.runToken)\n\t\t\t.where(\"reconciliation.target_epoch\", \"=\", input.targetEpoch)\n\t\t\t.where(\"reconciliation.state\", \"=\", \"leased\")\n\t\t\t.where(\"reconciliation.phase\", \"=\", \"scan\")\n\t\t\t.where(\"reconciliation.lease_token\", \"=\", input.claim.leaseToken)\n\t\t\t.where(liveLease(this.db))\n\t\t\t.where(this.statusOwnsRun(input.claim, input.targetEpoch));\n\t\tquery = input.previousCursor\n\t\t\t? query.where(\"reconciliation.scan_cursor\", \"=\", input.previousCursor)\n\t\t\t: query.where(\"reconciliation.scan_cursor\", \"is\", null);\n\t\tconst result = await query.executeTakeFirst();\n\t\treturn Number(result.numUpdatedRows ?? 0) === 1;\n\t}\n\n\tasync ownsRun(\n\t\tclaim: MediaUsageReconciliationClaim,\n\t\ttargetEpoch: number | string,\n\t): Promise<boolean> {\n\t\tconst result = await sql<{ owned: boolean | number }>`\n\t\t\tSELECT ${this.statusOwnsRun(claim, targetEpoch)} AS owned\n\t\t`.execute(this.db);\n\t\treturn Boolean(result.rows[0]?.owned);\n\t}\n\n\tasync restartRun(\n\t\tclaim: MediaUsageReconciliationClaim,\n\t\tpreviousEpoch: number | string,\n\t): Promise<number | string | null> {\n\t\tconst now = timestampOffset(this.db, 0);\n\t\tconst interruptedRestart = sql<boolean>`status = 'running'\n\t\t\tAND cursor = ${claim.runToken}\n\t\t\tAND change_epoch > ${previousEpoch}`;\n\t\tconst row = await this.db\n\t\t\t.updateTable(\"_emdash_media_usage_index_status as status\")\n\t\t\t.set({\n\t\t\t\tstatus: \"running\",\n\t\t\t\tstarted_at: sql<\n\t\t\t\t\tstring | null\n\t\t\t\t>`CASE WHEN ${interruptedRestart} THEN started_at ELSE ${now} END`,\n\t\t\t\tcompleted_at: null,\n\t\t\t\tcursor: claim.runToken,\n\t\t\t\tlast_error_code: null,\n\t\t\t\tchange_epoch: sql<number>`CASE WHEN ${interruptedRestart} THEN change_epoch ELSE change_epoch + 1 END`,\n\t\t\t\treconciliation_required: 1,\n\t\t\t\tupdated_at: now,\n\t\t\t})\n\t\t\t.where(\"status.adapter_id\", \"=\", CONTENT_ADAPTER_ID)\n\t\t\t.where(\"status.scope_type\", \"=\", COLLECTION_SCOPE)\n\t\t\t.where(\"status.collection_id\", \"=\", claim.collectionId)\n\t\t\t.where(\"status.scope_key\", \"=\", claim.collectionSlug)\n\t\t\t.where(\"status.capture_state\", \"=\", \"active\")\n\t\t\t.where(\"status.reconciliation_required\", \"=\", 1)\n\t\t\t.where((eb) =>\n\t\t\t\teb.or([eb(\"status.status\", \"!=\", \"running\"), eb(\"status.cursor\", \"=\", claim.runToken)]),\n\t\t\t)\n\t\t\t.where(this.liveClaimExists(claim))\n\t\t\t.returning(\"change_epoch\")\n\t\t\t.executeTakeFirst();\n\t\treturn row?.change_epoch ?? null;\n\t}\n\n\tasync restartScan(input: {\n\t\tclaim: MediaUsageReconciliationClaim;\n\t\tpreviousEpoch: number | string;\n\t\ttargetEpoch: number | string;\n\t\tfieldFingerprint: string;\n\t\tscanUpperId: string | null;\n\t}): Promise<boolean> {\n\t\tconst result = await this.db\n\t\t\t.updateTable(\"_emdash_media_usage_reconciliations as reconciliation\")\n\t\t\t.set({\n\t\t\t\ttarget_epoch: input.targetEpoch,\n\t\t\t\tfield_fingerprint: input.fieldFingerprint,\n\t\t\t\tphase: \"scan\",\n\t\t\t\tscan_cursor: null,\n\t\t\t\tscan_upper_id: input.scanUpperId,\n\t\t\t\tsource_cursor: null,\n\t\t\t\tsource_upper_key: null,\n\t\t\t\tattempt_count: 0,\n\t\t\t\tnext_attempt_at: timestampOffset(this.db, 0),\n\t\t\t\tlast_error_code: null,\n\t\t\t\tupdated_at: timestampOffset(this.db, 0),\n\t\t\t})\n\t\t\t.where(\"reconciliation.collection_id\", \"=\", input.claim.collectionId)\n\t\t\t.where(\"reconciliation.run_token\", \"=\", input.claim.runToken)\n\t\t\t.where(\"reconciliation.target_epoch\", \"=\", input.previousEpoch)\n\t\t\t.where(\"reconciliation.state\", \"=\", \"leased\")\n\t\t\t.where(\"reconciliation.lease_token\", \"=\", input.claim.leaseToken)\n\t\t\t.where(liveLease(this.db))\n\t\t\t.where(this.statusOwnsRun(input.claim, input.targetEpoch))\n\t\t\t.executeTakeFirst();\n\t\treturn Number(result.numUpdatedRows ?? 0) === 1;\n\t}\n\n\tasync findWorkBarrier(collectionId: string): Promise<MediaUsageReconciliationWorkBarrier> {\n\t\tif (!collectionId) throw new Error(\"Reconciliation work barrier requires a collection ID\");\n\t\tconst failed = await this.db\n\t\t\t.selectFrom(\"_emdash_media_usage_work\")\n\t\t\t.select(\"last_error_code\")\n\t\t\t.where(\"collection_id\", \"=\", collectionId)\n\t\t\t.where(\"state\", \"=\", \"failed\")\n\t\t\t.orderBy(\"content_id\")\n\t\t\t.limit(1)\n\t\t\t.executeTakeFirst();\n\t\tif (failed) {\n\t\t\treturn {\n\t\t\t\tstate: \"failed\",\n\t\t\t\terrorCode: failed.last_error_code ?? \"MEDIA_USAGE_PROCESSING_FAILED\",\n\t\t\t};\n\t\t}\n\t\tconst pending = await this.db\n\t\t\t.selectFrom(\"_emdash_media_usage_work\")\n\t\t\t.select(\"content_id\")\n\t\t\t.where(\"collection_id\", \"=\", collectionId)\n\t\t\t.limit(1)\n\t\t\t.executeTakeFirst();\n\t\treturn pending ? { state: \"pending\" } : { state: \"empty\" };\n\t}\n\n\tasync findSourceUpperKey(\n\t\tclaim: MediaUsageReconciliationClaim,\n\t\ttargetEpoch: number | string,\n\t): Promise<string | null> {\n\t\tconst row = await this.db\n\t\t\t.selectFrom(\"_emdash_media_usage_sources as source\")\n\t\t\t.select(\"source.source_key\")\n\t\t\t.where(\"source.source_type\", \"=\", \"content\")\n\t\t\t.where(\"source.collection_id\", \"=\", claim.collectionId)\n\t\t\t.where(\"source.identity_version\", \"=\", 1)\n\t\t\t.where(this.liveClaimExists(claim))\n\t\t\t.where(this.statusOwnsRun(claim, targetEpoch))\n\t\t\t.orderBy(\"source.source_key\", \"desc\")\n\t\t\t.limit(1)\n\t\t\t.executeTakeFirst();\n\t\treturn row?.source_key ?? null;\n\t}\n\n\tasync transitionToSources(input: {\n\t\tclaim: MediaUsageReconciliationClaim;\n\t\ttargetEpoch: number | string;\n\t\tfieldFingerprint: string;\n\t\tsourceUpperKey: string | null;\n\t}): Promise<boolean> {\n\t\tconst result = await this.db\n\t\t\t.updateTable(\"_emdash_media_usage_reconciliations as reconciliation\")\n\t\t\t.set({\n\t\t\t\tphase: \"sources\",\n\t\t\t\tsource_cursor: null,\n\t\t\t\tsource_upper_key: input.sourceUpperKey,\n\t\t\t\tattempt_count: 0,\n\t\t\t\tlast_error_code: null,\n\t\t\t\tupdated_at: timestampOffset(this.db, 0),\n\t\t\t})\n\t\t\t.where(\"reconciliation.collection_id\", \"=\", input.claim.collectionId)\n\t\t\t.where(\"reconciliation.run_token\", \"=\", input.claim.runToken)\n\t\t\t.where(\"reconciliation.target_epoch\", \"=\", input.targetEpoch)\n\t\t\t.where(\"reconciliation.field_fingerprint\", \"=\", input.fieldFingerprint)\n\t\t\t.where(\"reconciliation.state\", \"=\", \"leased\")\n\t\t\t.where(\"reconciliation.phase\", \"=\", \"scan\")\n\t\t\t.where(\"reconciliation.lease_token\", \"=\", input.claim.leaseToken)\n\t\t\t.where(liveLease(this.db))\n\t\t\t.where(this.statusOwnsRun(input.claim, input.targetEpoch))\n\t\t\t.where((eb) =>\n\t\t\t\teb.not(\n\t\t\t\t\teb.exists(\n\t\t\t\t\t\teb\n\t\t\t\t\t\t\t.selectFrom(\"_emdash_media_usage_work as work\")\n\t\t\t\t\t\t\t.select(\"work.content_id\")\n\t\t\t\t\t\t\t.where(\"work.collection_id\", \"=\", input.claim.collectionId),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t)\n\t\t\t.executeTakeFirst();\n\t\treturn Number(result.numUpdatedRows ?? 0) === 1;\n\t}\n\n\tasync findSourcePage(\n\t\treconciliation: MediaUsageReconciliationRecord,\n\t\tlimit: number,\n\t): Promise<MediaUsageReconciliationSourceCandidate[]> {\n\t\tif (!Number.isSafeInteger(limit) || limit < 1 || limit > 50) {\n\t\t\tthrow new Error(\"Reconciliation source page limit must be from 1 to 50\");\n\t\t}\n\t\tif (!reconciliation.leaseToken || reconciliation.targetEpoch === null) return [];\n\t\tlet query = this.db\n\t\t\t.selectFrom(\"_emdash_media_usage_sources as source\")\n\t\t\t.select([\"source.source_key\", \"source.content_id\", \"source.source_variant\"])\n\t\t\t.where(\"source.source_type\", \"=\", \"content\")\n\t\t\t.where(\"source.collection_id\", \"=\", reconciliation.collectionId)\n\t\t\t.where(\"source.identity_version\", \"=\", 1)\n\t\t\t.where(this.liveClaimExists(reconciliation))\n\t\t\t.where(this.statusOwnsRun(reconciliation, reconciliation.targetEpoch));\n\t\tif (reconciliation.sourceCursor) {\n\t\t\tquery = query.where(\"source.source_key\", \">\", reconciliation.sourceCursor);\n\t\t}\n\t\tif (reconciliation.sourceUpperKey) {\n\t\t\tquery = query.where(\"source.source_key\", \"<=\", reconciliation.sourceUpperKey);\n\t\t} else {\n\t\t\tquery = query.where(sql<boolean>`1 = 0`);\n\t\t}\n\t\tconst rows = await query.orderBy(\"source.source_key\").limit(limit).execute();\n\t\treturn rows.map((row) => ({\n\t\t\tsourceKey: row.source_key,\n\t\t\tcontentId: row.content_id,\n\t\t\tsourceVariant: row.source_variant,\n\t\t}));\n\t}\n\n\tasync findMissingContentIds(\n\t\tcollectionSlug: string,\n\t\tcontentIds: readonly string[],\n\t): Promise<string[]> {\n\t\tconst unique = [...new Set(contentIds)];\n\t\tif (unique.length === 0) return [];\n\t\tif (unique.length > 50 || unique.some((contentId) => !contentId)) {\n\t\t\tthrow new Error(\"Reconciliation source page has invalid content identity\");\n\t\t}\n\t\tconst tableName = contentTableName(collectionSlug);\n\t\tconst existing = await sql<{ id: string }>`\n\t\t\tSELECT id FROM ${sql.ref(tableName)} WHERE id IN (${sql.join(unique)})\n\t\t`.execute(this.db);\n\t\tconst present = new Set(existing.rows.map((row) => row.id));\n\t\treturn unique.filter((contentId) => !present.has(contentId));\n\t}\n\n\tasync checkpointSources(input: {\n\t\tclaim: MediaUsageReconciliationClaim;\n\t\ttargetEpoch: number | string;\n\t\tpreviousCursor: string | null;\n\t\tnextCursor: string;\n\t}): Promise<boolean> {\n\t\tlet query = this.db\n\t\t\t.updateTable(\"_emdash_media_usage_reconciliations as reconciliation\")\n\t\t\t.set({\n\t\t\t\tsource_cursor: input.nextCursor,\n\t\t\t\tattempt_count: 0,\n\t\t\t\tlast_error_code: null,\n\t\t\t\tupdated_at: timestampOffset(this.db, 0),\n\t\t\t})\n\t\t\t.where(\"reconciliation.collection_id\", \"=\", input.claim.collectionId)\n\t\t\t.where(\"reconciliation.run_token\", \"=\", input.claim.runToken)\n\t\t\t.where(\"reconciliation.target_epoch\", \"=\", input.targetEpoch)\n\t\t\t.where(\"reconciliation.state\", \"=\", \"leased\")\n\t\t\t.where(\"reconciliation.phase\", \"=\", \"sources\")\n\t\t\t.where(\"reconciliation.lease_token\", \"=\", input.claim.leaseToken)\n\t\t\t.where(liveLease(this.db))\n\t\t\t.where(this.statusOwnsRun(input.claim, input.targetEpoch));\n\t\tquery = input.previousCursor\n\t\t\t? query.where(\"reconciliation.source_cursor\", \"=\", input.previousCursor)\n\t\t\t: query.where(\"reconciliation.source_cursor\", \"is\", null);\n\t\tconst result = await query.executeTakeFirst();\n\t\treturn Number(result.numUpdatedRows ?? 0) === 1;\n\t}\n\n\tasync finishFailedCoverage(collectionId: string, runToken: string): Promise<boolean> {\n\t\tassertIdentity({ collectionId, runToken });\n\t\tconst now = timestampOffset(this.db, 0);\n\t\tconst result = await this.db\n\t\t\t.updateTable(\"_emdash_media_usage_index_status as status\")\n\t\t\t.set({\n\t\t\t\tstatus: sql<string>`CASE WHEN EXISTS (\n\t\t\t\t\tSELECT 1 FROM _emdash_media_usage_sources AS source\n\t\t\t\t\tWHERE source.source_type = 'content'\n\t\t\t\t\t\tAND source.collection_id = ${collectionId}\n\t\t\t\t\t\tAND source.identity_version = 1\n\t\t\t\t\tLIMIT 1\n\t\t\t\t) THEN 'partial' ELSE 'failed' END`,\n\t\t\t\tcompleted_at: null,\n\t\t\t\tcursor: null,\n\t\t\t\tlast_error_code: sql<string>`CASE WHEN (\n\t\t\t\t\tSELECT reconciliation.last_error_code\n\t\t\t\t\tFROM _emdash_media_usage_reconciliations AS reconciliation\n\t\t\t\t\tWHERE reconciliation.collection_id = ${collectionId}\n\t\t\t\t\t\tAND reconciliation.run_token = ${runToken}\n\t\t\t\t) = 'MEDIA_USAGE_RECONCILIATION_ENTRY_FAILED'\n\t\t\t\tTHEN COALESCE(\n\t\t\t\t\t(SELECT work.last_error_code\n\t\t\t\t\t FROM _emdash_media_usage_work AS work\n\t\t\t\t\t WHERE work.collection_id = ${collectionId} AND work.state = 'failed'\n\t\t\t\t\t ORDER BY work.content_id LIMIT 1),\n\t\t\t\t\t'MEDIA_USAGE_RECONCILIATION_ENTRY_FAILED'\n\t\t\t\t)\n\t\t\t\tELSE (\n\t\t\t\t\tSELECT reconciliation.last_error_code\n\t\t\t\t\tFROM _emdash_media_usage_reconciliations AS reconciliation\n\t\t\t\t\tWHERE reconciliation.collection_id = ${collectionId}\n\t\t\t\t\t\tAND reconciliation.run_token = ${runToken}\n\t\t\t\t) END`,\n\t\t\t\treconciliation_required: 1,\n\t\t\t\tupdated_at: now,\n\t\t\t})\n\t\t\t.where(\"status.adapter_id\", \"=\", CONTENT_ADAPTER_ID)\n\t\t\t.where(\"status.scope_type\", \"=\", COLLECTION_SCOPE)\n\t\t\t.where(\"status.collection_id\", \"=\", collectionId)\n\t\t\t.where(\"status.status\", \"=\", \"running\")\n\t\t\t.where(\"status.cursor\", \"=\", runToken)\n\t\t\t.where((eb) =>\n\t\t\t\teb.exists(\n\t\t\t\t\teb\n\t\t\t\t\t\t.selectFrom(\"_emdash_media_usage_reconciliations as reconciliation\")\n\t\t\t\t\t\t.select(\"reconciliation.collection_id\")\n\t\t\t\t\t\t.where(\"reconciliation.collection_id\", \"=\", collectionId)\n\t\t\t\t\t\t.where(\"reconciliation.run_token\", \"=\", runToken)\n\t\t\t\t\t\t.where(\"reconciliation.state\", \"=\", \"failed\"),\n\t\t\t\t),\n\t\t\t)\n\t\t\t.executeTakeFirst();\n\t\treturn Number(result.numUpdatedRows ?? 0) === 1;\n\t}\n\n\tasync finalizeCoverage(input: {\n\t\tclaim: MediaUsageReconciliationClaim;\n\t\ttargetEpoch: number | string;\n\t\tfieldFingerprint: string;\n\t\tschemaVersion: number;\n\t}): Promise<boolean> {\n\t\tconst now = timestampOffset(this.db, 0);\n\t\tconst result = await this.db\n\t\t\t.updateTable(\"_emdash_media_usage_index_status as status\")\n\t\t\t.set({\n\t\t\t\tstatus: \"complete\",\n\t\t\t\tschema_version: input.schemaVersion,\n\t\t\t\tcompleted_at: now,\n\t\t\t\tcursor: null,\n\t\t\t\tlast_error_code: null,\n\t\t\t\treconciliation_required: 0,\n\t\t\t\tupdated_at: now,\n\t\t\t})\n\t\t\t.where(\"status.adapter_id\", \"=\", CONTENT_ADAPTER_ID)\n\t\t\t.where(\"status.scope_type\", \"=\", COLLECTION_SCOPE)\n\t\t\t.where(\"status.collection_id\", \"=\", input.claim.collectionId)\n\t\t\t.where(\"status.scope_key\", \"=\", input.claim.collectionSlug)\n\t\t\t.where(\"status.capture_state\", \"=\", \"active\")\n\t\t\t.where(\"status.reconciliation_required\", \"=\", 1)\n\t\t\t.where(\"status.status\", \"=\", \"running\")\n\t\t\t.where(\"status.cursor\", \"=\", input.claim.runToken)\n\t\t\t.where(\"status.change_epoch\", \"=\", input.targetEpoch)\n\t\t\t.where((eb) =>\n\t\t\t\teb.not(\n\t\t\t\t\teb.exists(\n\t\t\t\t\t\teb\n\t\t\t\t\t\t\t.selectFrom(\"_emdash_media_usage_work as work\")\n\t\t\t\t\t\t\t.select(\"work.content_id\")\n\t\t\t\t\t\t\t.where(\"work.collection_id\", \"=\", input.claim.collectionId),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t)\n\t\t\t.where((eb) =>\n\t\t\t\teb.exists(\n\t\t\t\t\teb\n\t\t\t\t\t\t.selectFrom(\"_emdash_media_usage_reconciliations as reconciliation\")\n\t\t\t\t\t\t.innerJoin(\"_emdash_collections as collection\", (join) =>\n\t\t\t\t\t\t\tjoin\n\t\t\t\t\t\t\t\t.onRef(\"collection.id\", \"=\", \"reconciliation.collection_id\")\n\t\t\t\t\t\t\t\t.onRef(\"collection.slug\", \"=\", \"reconciliation.collection_slug\"),\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.select(\"reconciliation.collection_id\")\n\t\t\t\t\t\t.where(\"reconciliation.collection_id\", \"=\", input.claim.collectionId)\n\t\t\t\t\t\t.where(\"reconciliation.run_token\", \"=\", input.claim.runToken)\n\t\t\t\t\t\t.where(\"reconciliation.target_epoch\", \"=\", input.targetEpoch)\n\t\t\t\t\t\t.where(\"reconciliation.field_fingerprint\", \"=\", input.fieldFingerprint)\n\t\t\t\t\t\t.where(\"reconciliation.state\", \"=\", \"leased\")\n\t\t\t\t\t\t.where(\"reconciliation.phase\", \"=\", \"sources\")\n\t\t\t\t\t\t.where(\"reconciliation.lease_token\", \"=\", input.claim.leaseToken)\n\t\t\t\t\t\t.where(liveLease(this.db, \"reconciliation.lease_expires_at\"))\n\t\t\t\t\t\t.where((inner) =>\n\t\t\t\t\t\t\tinner.not(\n\t\t\t\t\t\t\t\tinner.exists(\n\t\t\t\t\t\t\t\t\tinner\n\t\t\t\t\t\t\t\t\t\t.selectFrom(\"_emdash_media_usage_collection_deletions as deletion\")\n\t\t\t\t\t\t\t\t\t\t.select(\"deletion.collection_id\")\n\t\t\t\t\t\t\t\t\t\t.where(\"deletion.collection_id\", \"=\", input.claim.collectionId),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t)\n\t\t\t.where((eb) =>\n\t\t\t\teb.exists(\n\t\t\t\t\teb\n\t\t\t\t\t\t.selectFrom(\"_emdash_media_usage_activation as activation\")\n\t\t\t\t\t\t.select(\"activation.task_key\")\n\t\t\t\t\t\t.where(\"activation.task_key\", \"=\", ACTIVATION_KEY)\n\t\t\t\t\t\t.where(\"activation.state\", \"=\", \"active\"),\n\t\t\t\t),\n\t\t\t)\n\t\t\t.executeTakeFirst();\n\t\treturn Number(result.numUpdatedRows ?? 0) === 1;\n\t}\n\n\tasync deleteFinalized(claim: MediaUsageReconciliationClaim): Promise<boolean> {\n\t\tconst result = await this.db\n\t\t\t.deleteFrom(\"_emdash_media_usage_reconciliations as reconciliation\")\n\t\t\t.where(\"reconciliation.collection_id\", \"=\", claim.collectionId)\n\t\t\t.where(\"reconciliation.run_token\", \"=\", claim.runToken)\n\t\t\t.where(\"reconciliation.state\", \"=\", \"leased\")\n\t\t\t.where(\"reconciliation.lease_token\", \"=\", claim.leaseToken)\n\t\t\t.where((eb) =>\n\t\t\t\teb.exists(\n\t\t\t\t\teb\n\t\t\t\t\t\t.selectFrom(\"_emdash_media_usage_index_status as status\")\n\t\t\t\t\t\t.select(\"status.collection_id\")\n\t\t\t\t\t\t.where(\"status.collection_id\", \"=\", claim.collectionId)\n\t\t\t\t\t\t.where(\"status.scope_key\", \"=\", claim.collectionSlug)\n\t\t\t\t\t\t.where(\"status.status\", \"=\", \"complete\")\n\t\t\t\t\t\t.where(\"status.reconciliation_required\", \"=\", 0),\n\t\t\t\t),\n\t\t\t)\n\t\t\t.executeTakeFirst();\n\t\treturn Number(result.numDeletedRows ?? 0) === 1;\n\t}\n\n\tasync deleteOneObsolete(): Promise<boolean> {\n\t\tconst result = await sql<{ collection_id: string }>`\n\t\t\tDELETE FROM _emdash_media_usage_reconciliations\n\t\t\tWHERE (collection_id, run_token) IN (\n\t\t\t\tSELECT reconciliation.collection_id, reconciliation.run_token\n\t\t\t\tFROM _emdash_media_usage_reconciliations AS reconciliation\n\t\t\t\tINNER JOIN _emdash_media_usage_index_status AS status\n\t\t\t\t\tON status.collection_id = reconciliation.collection_id\n\t\t\t\t\tAND status.scope_key = reconciliation.collection_slug\n\t\t\t\tWHERE status.adapter_id = ${CONTENT_ADAPTER_ID}\n\t\t\t\t\tAND status.scope_type = ${COLLECTION_SCOPE}\n\t\t\t\t\tAND status.reconciliation_required = 0\n\t\t\t\tORDER BY reconciliation.updated_at, reconciliation.collection_id\n\t\t\t\tLIMIT 1\n\t\t\t)\n\t\t\tRETURNING collection_id\n\t\t`.execute(this.db);\n\t\treturn result.rows.length === 1;\n\t}\n\n\tprivate liveClaimExists(\n\t\tclaim: Pick<\n\t\t\tMediaUsageReconciliationRecord,\n\t\t\t\"collectionId\" | \"collectionSlug\" | \"runToken\" | \"leaseToken\"\n\t\t>,\n\t): RawBuilder<boolean> {\n\t\treturn this.liveClaimExistsSql(claim);\n\t}\n\n\tprivate liveClaimExistsSql(\n\t\tclaim: Pick<\n\t\t\tMediaUsageReconciliationRecord,\n\t\t\t\"collectionId\" | \"collectionSlug\" | \"runToken\" | \"leaseToken\"\n\t\t>,\n\t): RawBuilder<boolean> {\n\t\treturn sql<boolean>`EXISTS (\n\t\t\tSELECT 1\n\t\t\tFROM _emdash_media_usage_reconciliations AS reconciliation\n\t\t\tINNER JOIN _emdash_collections AS collection\n\t\t\t\tON collection.id = reconciliation.collection_id\n\t\t\t\tAND collection.slug = reconciliation.collection_slug\n\t\t\tWHERE reconciliation.collection_id = ${claim.collectionId}\n\t\t\t\tAND reconciliation.collection_slug = ${claim.collectionSlug}\n\t\t\t\tAND reconciliation.run_token = ${claim.runToken}\n\t\t\t\tAND reconciliation.state = 'leased'\n\t\t\t\tAND reconciliation.lease_token = ${claim.leaseToken}\n\t\t\t\tAND ${liveLease(this.db, \"reconciliation.lease_expires_at\")}\n\t\t\t\tAND EXISTS (\n\t\t\t\t\tSELECT 1 FROM _emdash_media_usage_activation AS activation\n\t\t\t\t\tWHERE activation.task_key = ${ACTIVATION_KEY}\n\t\t\t\t\t\tAND activation.state = 'active'\n\t\t\t\t)\n\t\t\t\tAND NOT EXISTS (\n\t\t\t\t\tSELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion\n\t\t\t\t\tWHERE deletion.collection_id = reconciliation.collection_id\n\t\t\t\t)\n\t\t)`;\n\t}\n\n\tprivate statusOwnsRun(\n\t\tclaim: Pick<MediaUsageReconciliationRecord, \"collectionId\" | \"collectionSlug\" | \"runToken\">,\n\t\ttargetEpoch: number | string,\n\t): RawBuilder<boolean> {\n\t\treturn sql<boolean>`EXISTS (\n\t\t\tSELECT 1 FROM _emdash_media_usage_index_status AS status\n\t\t\tWHERE status.adapter_id = ${CONTENT_ADAPTER_ID}\n\t\t\t\tAND status.scope_type = ${COLLECTION_SCOPE}\n\t\t\t\tAND status.collection_id = ${claim.collectionId}\n\t\t\t\tAND status.scope_key = ${claim.collectionSlug}\n\t\t\t\tAND status.capture_state = 'active'\n\t\t\t\tAND status.reconciliation_required = 1\n\t\t\t\tAND status.status = 'running'\n\t\t\t\tAND status.cursor = ${claim.runToken}\n\t\t\t\tAND status.change_epoch = ${targetEpoch}\n\t\t)`;\n\t}\n\n\tasync seedNextCandidate(): Promise<boolean> {\n\t\tconst runToken = ulid();\n\t\tconst now = timestampOffset(this.db, 0);\n\t\tconst result = await sql<{ collection_id: string }>`\n\t\t\tINSERT INTO _emdash_media_usage_reconciliations (\n\t\t\t\tcollection_id,\n\t\t\t\tcollection_slug,\n\t\t\t\trun_token,\n\t\t\t\tnext_attempt_at,\n\t\t\t\tupdated_at\n\t\t\t)\n\t\t\tSELECT status.collection_id, status.scope_key, ${runToken}, ${now}, ${now}\n\t\t\tFROM _emdash_media_usage_index_status AS status\n\t\t\tINNER JOIN _emdash_collections AS collection\n\t\t\t\tON collection.id = status.collection_id\n\t\t\t\tAND collection.slug = status.scope_key\n\t\t\tWHERE status.adapter_id = ${CONTENT_ADAPTER_ID}\n\t\t\t\tAND status.scope_type = ${COLLECTION_SCOPE}\n\t\t\t\tAND status.capture_state = 'active'\n\t\t\t\tAND status.reconciliation_required = 1\n\t\t\t\tAND status.collection_id IS NOT NULL\n\t\t\t\tAND EXISTS (\n\t\t\t\t\tSELECT 1 FROM _emdash_media_usage_activation AS activation\n\t\t\t\t\tWHERE activation.task_key = ${ACTIVATION_KEY}\n\t\t\t\t\t\tAND activation.state = 'active'\n\t\t\t\t)\n\t\t\t\tAND NOT EXISTS (\n\t\t\t\t\tSELECT 1 FROM _emdash_media_usage_reconciliations AS existing\n\t\t\t\t\tWHERE existing.collection_id = status.collection_id\n\t\t\t\t)\n\t\t\t\tAND NOT EXISTS (\n\t\t\t\t\tSELECT 1 FROM _emdash_media_usage_collection_deletions AS deletion\n\t\t\t\t\tWHERE deletion.collection_id = status.collection_id\n\t\t\t\t)\n\t\t\tORDER BY status.collection_id\n\t\t\tLIMIT 1\n\t\t\tON CONFLICT (collection_id) DO NOTHING\n\t\t\tRETURNING collection_id\n\t\t`.execute(this.db);\n\t\treturn result.rows.length === 1;\n\t}\n\n\tasync findDue(limit: number): Promise<MediaUsageReconciliationRecord[]> {\n\t\tassertLimit(limit);\n\t\tconst nextAttemptIsDue = timestampIsDue(this.db, \"next_attempt_at\");\n\t\tconst leaseIsDue = timestampIsDue(this.db, \"lease_expires_at\");\n\t\tconst result = await sql<Selectable<MediaUsageReconciliationTable>>`\n\t\t\tWITH pending_candidates AS (\n\t\t\t\tSELECT * FROM _emdash_media_usage_reconciliations\n\t\t\t\tWHERE state = 'pending' AND ${nextAttemptIsDue}\n\t\t\t\tORDER BY next_attempt_at, updated_at, collection_id\n\t\t\t\tLIMIT ${limit}\n\t\t\t), retry_candidates AS (\n\t\t\t\tSELECT * FROM _emdash_media_usage_reconciliations\n\t\t\t\tWHERE state = 'retry' AND ${nextAttemptIsDue}\n\t\t\t\tORDER BY next_attempt_at, updated_at, collection_id\n\t\t\t\tLIMIT ${limit}\n\t\t\t), leased_candidates AS (\n\t\t\t\tSELECT * FROM _emdash_media_usage_reconciliations\n\t\t\t\tWHERE state = 'leased' AND ${leaseIsDue}\n\t\t\t\tORDER BY lease_expires_at, updated_at, collection_id\n\t\t\t\tLIMIT ${limit}\n\t\t\t), candidates AS (\n\t\t\t\tSELECT * FROM pending_candidates\n\t\t\t\tUNION ALL SELECT * FROM retry_candidates\n\t\t\t\tUNION ALL SELECT * FROM leased_candidates\n\t\t\t)\n\t\t\tSELECT * FROM candidates\n\t\t\tORDER BY CASE WHEN state = 'leased' THEN lease_expires_at ELSE next_attempt_at END,\n\t\t\t\tupdated_at,\n\t\t\t\tcollection_id\n\t\t\tLIMIT ${limit}\n\t\t`.execute(this.db);\n\t\treturn result.rows.map(rowToRecord);\n\t}\n\n\tasync findFailed(limit: number): Promise<MediaUsageReconciliationRecord[]> {\n\t\tassertLimit(limit);\n\t\tconst rows = await this.db\n\t\t\t.selectFrom(\"_emdash_media_usage_reconciliations as reconciliation\")\n\t\t\t.innerJoin(\"_emdash_media_usage_index_status as status\", (join) =>\n\t\t\t\tjoin\n\t\t\t\t\t.onRef(\"status.collection_id\", \"=\", \"reconciliation.collection_id\")\n\t\t\t\t\t.onRef(\"status.scope_key\", \"=\", \"reconciliation.collection_slug\"),\n\t\t\t)\n\t\t\t.selectAll(\"reconciliation\")\n\t\t\t.where(\"reconciliation.state\", \"=\", \"failed\")\n\t\t\t.where(\"status.adapter_id\", \"=\", CONTENT_ADAPTER_ID)\n\t\t\t.where(\"status.scope_type\", \"=\", COLLECTION_SCOPE)\n\t\t\t.where((eb) =>\n\t\t\t\teb.or([\n\t\t\t\t\teb(\"status.reconciliation_required\", \"=\", 0),\n\t\t\t\t\teb.and([\n\t\t\t\t\t\teb(\"status.status\", \"=\", \"running\"),\n\t\t\t\t\t\teb(\"status.cursor\", \"=\", eb.ref(\"reconciliation.run_token\")),\n\t\t\t\t\t]),\n\t\t\t\t\teb.and([\n\t\t\t\t\t\teb(\"status.cursor\", \"is\", null),\n\t\t\t\t\t\teb(\"status.change_epoch\", \">\", eb.ref(\"reconciliation.target_epoch\")),\n\t\t\t\t\t]),\n\t\t\t\t]),\n\t\t\t)\n\t\t\t.orderBy(\"reconciliation.updated_at\")\n\t\t\t.orderBy(\"reconciliation.collection_id\")\n\t\t\t.limit(limit)\n\t\t\t.execute();\n\t\treturn rows.map(rowToRecord);\n\t}\n\n\tasync claim(input: {\n\t\tcollectionId: string;\n\t\trunToken: string;\n\t\tleaseDurationSeconds: number;\n\t}): Promise<MediaUsageReconciliationClaim | null> {\n\t\tassertIdentity(input);\n\t\tassertDuration(input.leaseDurationSeconds, \"lease duration\");\n\t\tconst leaseToken = ulid();\n\t\tconst row = await this.db\n\t\t\t.updateTable(\"_emdash_media_usage_reconciliations as reconciliation\")\n\t\t\t.set({\n\t\t\t\tstate: \"leased\",\n\t\t\t\tlease_token: leaseToken,\n\t\t\t\tlease_expires_at: timestampOffset(this.db, input.leaseDurationSeconds),\n\t\t\t\tupdated_at: timestampOffset(this.db, 0),\n\t\t\t})\n\t\t\t.where(\"reconciliation.collection_id\", \"=\", input.collectionId)\n\t\t\t.where(\"reconciliation.run_token\", \"=\", input.runToken)\n\t\t\t.where((eb) =>\n\t\t\t\teb.or([\n\t\t\t\t\teb.and([\n\t\t\t\t\t\teb(\"reconciliation.state\", \"in\", [\"pending\", \"retry\"]),\n\t\t\t\t\t\ttimestampIsDue(this.db, \"reconciliation.next_attempt_at\"),\n\t\t\t\t\t]),\n\t\t\t\t\teb.and([\n\t\t\t\t\t\teb(\"reconciliation.state\", \"=\", \"leased\"),\n\t\t\t\t\t\ttimestampIsDue(this.db, \"reconciliation.lease_expires_at\"),\n\t\t\t\t\t]),\n\t\t\t\t]),\n\t\t\t)\n\t\t\t.where((eb) =>\n\t\t\t\teb.exists(\n\t\t\t\t\teb\n\t\t\t\t\t\t.selectFrom(\"_emdash_media_usage_activation as activation\")\n\t\t\t\t\t\t.select(\"activation.task_key\")\n\t\t\t\t\t\t.where(\"activation.task_key\", \"=\", ACTIVATION_KEY)\n\t\t\t\t\t\t.where(\"activation.state\", \"=\", \"active\"),\n\t\t\t\t),\n\t\t\t)\n\t\t\t.where((eb) =>\n\t\t\t\teb.exists(\n\t\t\t\t\teb\n\t\t\t\t\t\t.selectFrom(\"_emdash_media_usage_index_status as status\")\n\t\t\t\t\t\t.innerJoin(\"_emdash_collections as collection\", (join) =>\n\t\t\t\t\t\t\tjoin\n\t\t\t\t\t\t\t\t.onRef(\"collection.id\", \"=\", \"status.collection_id\")\n\t\t\t\t\t\t\t\t.onRef(\"collection.slug\", \"=\", \"status.scope_key\"),\n\t\t\t\t\t\t)\n\t\t\t\t\t\t.select(\"status.collection_id\")\n\t\t\t\t\t\t.whereRef(\"status.collection_id\", \"=\", \"reconciliation.collection_id\")\n\t\t\t\t\t\t.whereRef(\"status.scope_key\", \"=\", \"reconciliation.collection_slug\")\n\t\t\t\t\t\t.where(\"status.adapter_id\", \"=\", CONTENT_ADAPTER_ID)\n\t\t\t\t\t\t.where(\"status.scope_type\", \"=\", COLLECTION_SCOPE)\n\t\t\t\t\t\t.where(\"status.capture_state\", \"=\", \"active\")\n\t\t\t\t\t\t.where(\"status.reconciliation_required\", \"=\", 1),\n\t\t\t\t),\n\t\t\t)\n\t\t\t.where((eb) =>\n\t\t\t\teb.not(\n\t\t\t\t\teb.exists(\n\t\t\t\t\t\teb\n\t\t\t\t\t\t\t.selectFrom(\"_emdash_media_usage_collection_deletions as deletion\")\n\t\t\t\t\t\t\t.select(\"deletion.collection_id\")\n\t\t\t\t\t\t\t.whereRef(\"deletion.collection_id\", \"=\", \"reconciliation.collection_id\"),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t)\n\t\t\t.returningAll()\n\t\t\t.executeTakeFirst();\n\t\treturn row\n\t\t\t? ({ ...rowToRecord(row), leaseToken } satisfies MediaUsageReconciliationClaim)\n\t\t\t: null;\n\t}\n\n\tasync release(input: {\n\t\tcollectionId: string;\n\t\trunToken: string;\n\t\tleaseToken: string;\n\t\tdelaySeconds: number;\n\t}): Promise<boolean> {\n\t\tassertLeaseIdentity(input);\n\t\tassertDuration(input.delaySeconds, \"release delay\", true);\n\t\tconst result = await this.db\n\t\t\t.updateTable(\"_emdash_media_usage_reconciliations\")\n\t\t\t.set({\n\t\t\t\tstate: \"pending\",\n\t\t\t\tnext_attempt_at: timestampOffset(this.db, input.delaySeconds),\n\t\t\t\tlease_token: null,\n\t\t\t\tlease_expires_at: null,\n\t\t\t\tupdated_at: timestampOffset(this.db, 0),\n\t\t\t})\n\t\t\t.where(\"collection_id\", \"=\", input.collectionId)\n\t\t\t.where(\"run_token\", \"=\", input.runToken)\n\t\t\t.where(\"state\", \"=\", \"leased\")\n\t\t\t.where(\"lease_token\", \"=\", input.leaseToken)\n\t\t\t.where(liveLease(this.db))\n\t\t\t.executeTakeFirst();\n\t\treturn Number(result.numUpdatedRows ?? 0) === 1;\n\t}\n\n\tasync recordFailure(input: {\n\t\tcollectionId: string;\n\t\trunToken: string;\n\t\tleaseToken: string;\n\t\terrorCode: string;\n\t\tretryDelaySeconds: number;\n\t\tterminal: boolean;\n\t}): Promise<boolean> {\n\t\tassertLeaseIdentity(input);\n\t\tif (!STABLE_ERROR_CODE_PATTERN.test(input.errorCode)) {\n\t\t\tthrow new Error(\"Reconciliation failure requires a stable error code\");\n\t\t}\n\t\tassertDuration(input.retryDelaySeconds, \"retry delay\", true);\n\t\tconst result = await this.db\n\t\t\t.updateTable(\"_emdash_media_usage_reconciliations\")\n\t\t\t.set({\n\t\t\t\tstate: input.terminal\n\t\t\t\t\t? \"failed\"\n\t\t\t\t\t: sql<string>`CASE WHEN attempt_count >= 4 THEN 'failed' ELSE 'retry' END`,\n\t\t\t\t...(input.terminal\n\t\t\t\t\t? {\n\t\t\t\t\t\t\ttarget_epoch: sql<number | string | null>`COALESCE(\n\t\t\t\t\t\t\t\ttarget_epoch,\n\t\t\t\t\t\t\t\t(SELECT status.change_epoch\n\t\t\t\t\t\t\t\t FROM _emdash_media_usage_index_status AS status\n\t\t\t\t\t\t\t\t WHERE status.adapter_id = ${CONTENT_ADAPTER_ID}\n\t\t\t\t\t\t\t\t\tAND status.scope_type = ${COLLECTION_SCOPE}\n\t\t\t\t\t\t\t\t\tAND status.collection_id = ${input.collectionId}\n\t\t\t\t\t\t\t\t\tAND status.status = 'running'\n\t\t\t\t\t\t\t\t\tAND status.cursor = ${input.runToken})\n\t\t\t\t\t\t\t)`,\n\t\t\t\t\t\t}\n\t\t\t\t\t: {}),\n\t\t\t\tattempt_count: sql<number>`attempt_count + 1`,\n\t\t\t\tnext_attempt_at: timestampOffset(this.db, input.retryDelaySeconds),\n\t\t\t\tlease_token: null,\n\t\t\t\tlease_expires_at: null,\n\t\t\t\tlast_error_code: input.errorCode,\n\t\t\t\tupdated_at: timestampOffset(this.db, 0),\n\t\t\t})\n\t\t\t.where(\"collection_id\", \"=\", input.collectionId)\n\t\t\t.where(\"run_token\", \"=\", input.runToken)\n\t\t\t.where(\"state\", \"=\", \"leased\")\n\t\t\t.where(\"lease_token\", \"=\", input.leaseToken)\n\t\t\t.where(liveLease(this.db))\n\t\t\t.executeTakeFirst();\n\t\treturn Number(result.numUpdatedRows ?? 0) === 1;\n\t}\n\n\tasync recordEntryFailure(claim: MediaUsageReconciliationClaim): Promise<boolean> {\n\t\tconst result = await this.db\n\t\t\t.updateTable(\"_emdash_media_usage_reconciliations as reconciliation\")\n\t\t\t.set({\n\t\t\t\tstate: \"failed\",\n\t\t\t\tattempt_count: sql<number>`attempt_count + 1`,\n\t\t\t\tnext_attempt_at: timestampOffset(this.db, 0),\n\t\t\t\tlease_token: null,\n\t\t\t\tlease_expires_at: null,\n\t\t\t\tlast_error_code: \"MEDIA_USAGE_RECONCILIATION_ENTRY_FAILED\",\n\t\t\t\tupdated_at: timestampOffset(this.db, 0),\n\t\t\t})\n\t\t\t.where(\"reconciliation.collection_id\", \"=\", claim.collectionId)\n\t\t\t.where(\"reconciliation.run_token\", \"=\", claim.runToken)\n\t\t\t.where(\"reconciliation.state\", \"=\", \"leased\")\n\t\t\t.where(\"reconciliation.lease_token\", \"=\", claim.leaseToken)\n\t\t\t.where(liveLease(this.db))\n\t\t\t.where((eb) =>\n\t\t\t\teb.exists(\n\t\t\t\t\teb\n\t\t\t\t\t\t.selectFrom(\"_emdash_media_usage_work as work\")\n\t\t\t\t\t\t.select(\"work.content_id\")\n\t\t\t\t\t\t.where(\"work.collection_id\", \"=\", claim.collectionId)\n\t\t\t\t\t\t.where(\"work.state\", \"=\", \"failed\"),\n\t\t\t\t),\n\t\t\t)\n\t\t\t.executeTakeFirst();\n\t\treturn Number(result.numUpdatedRows ?? 0) === 1;\n\t}\n\n\tasync resetFailedForNewEpoch(\n\t\tobserved: Selectable<MediaUsageReconciliationTable> | MediaUsageReconciliationRecord,\n\t): Promise<boolean> {\n\t\tconst collectionId =\n\t\t\t\"collection_id\" in observed ? observed.collection_id : observed.collectionId;\n\t\tconst runToken = \"run_token\" in observed ? observed.run_token : observed.runToken;\n\t\tconst targetEpoch = \"target_epoch\" in observed ? observed.target_epoch : observed.targetEpoch;\n\t\tif (targetEpoch === null) return false;\n\t\tconst now = timestampOffset(this.db, 0);\n\t\tconst result = await this.db\n\t\t\t.updateTable(\"_emdash_media_usage_reconciliations as reconciliation\")\n\t\t\t.set({\n\t\t\t\tstate: \"pending\",\n\t\t\t\tphase: \"scan\",\n\t\t\t\ttarget_epoch: null,\n\t\t\t\tfield_fingerprint: null,\n\t\t\t\tscan_cursor: null,\n\t\t\t\tscan_upper_id: null,\n\t\t\t\tsource_cursor: null,\n\t\t\t\tsource_upper_key: null,\n\t\t\t\tattempt_count: 0,\n\t\t\t\tnext_attempt_at: now,\n\t\t\t\tlease_token: null,\n\t\t\t\tlease_expires_at: null,\n\t\t\t\tlast_error_code: null,\n\t\t\t\tupdated_at: now,\n\t\t\t})\n\t\t\t.where(\"reconciliation.collection_id\", \"=\", collectionId)\n\t\t\t.where(\"reconciliation.run_token\", \"=\", runToken)\n\t\t\t.where(\"reconciliation.state\", \"=\", \"failed\")\n\t\t\t.where(\"reconciliation.target_epoch\", \"=\", targetEpoch)\n\t\t\t.where((eb) =>\n\t\t\t\teb.exists(\n\t\t\t\t\teb\n\t\t\t\t\t\t.selectFrom(\"_emdash_media_usage_index_status as status\")\n\t\t\t\t\t\t.select(\"status.collection_id\")\n\t\t\t\t\t\t.whereRef(\"status.collection_id\", \"=\", \"reconciliation.collection_id\")\n\t\t\t\t\t\t.whereRef(\"status.scope_key\", \"=\", \"reconciliation.collection_slug\")\n\t\t\t\t\t\t.where(\"status.adapter_id\", \"=\", CONTENT_ADAPTER_ID)\n\t\t\t\t\t\t.where(\"status.scope_type\", \"=\", COLLECTION_SCOPE)\n\t\t\t\t\t\t.where(\"status.capture_state\", \"=\", \"active\")\n\t\t\t\t\t\t.where(\"status.reconciliation_required\", \"=\", 1)\n\t\t\t\t\t\t.where(\"status.cursor\", \"is\", null)\n\t\t\t\t\t\t.where(\"status.change_epoch\", \">\", targetEpoch),\n\t\t\t\t),\n\t\t\t)\n\t\t\t.executeTakeFirst();\n\t\treturn Number(result.numUpdatedRows ?? 0) === 1;\n\t}\n}\n\nfunction rowToRecord(\n\trow: Selectable<MediaUsageReconciliationTable>,\n): MediaUsageReconciliationRecord {\n\tif (!isState(row.state) || !isPhase(row.phase) || !Number.isSafeInteger(row.attempt_count)) {\n\t\tthrow new Error(\"Invalid media usage reconciliation lifecycle\");\n\t}\n\treturn {\n\t\tcollectionId: row.collection_id,\n\t\tcollectionSlug: row.collection_slug,\n\t\trunToken: row.run_token,\n\t\ttargetEpoch: row.target_epoch,\n\t\tfieldFingerprint: row.field_fingerprint,\n\t\tstate: row.state,\n\t\tphase: row.phase,\n\t\tscanCursor: row.scan_cursor,\n\t\tscanUpperId: row.scan_upper_id,\n\t\tsourceCursor: row.source_cursor,\n\t\tsourceUpperKey: row.source_upper_key,\n\t\tattemptCount: row.attempt_count,\n\t\tnextAttemptAt: row.next_attempt_at,\n\t\tleaseToken: row.lease_token,\n\t\tleaseExpiresAt: row.lease_expires_at,\n\t\tlastErrorCode: row.last_error_code,\n\t\tcreatedAt: row.created_at,\n\t\tupdatedAt: row.updated_at,\n\t};\n}\n\nfunction isState(state: string): state is MediaUsageReconciliationState {\n\treturn state === \"pending\" || state === \"retry\" || state === \"leased\" || state === \"failed\";\n}\n\nfunction isPhase(phase: string): phase is MediaUsageReconciliationPhase {\n\treturn phase === \"scan\" || phase === \"sources\";\n}\n\nfunction assertLimit(limit: number): void {\n\tif (!Number.isSafeInteger(limit) || limit < 1 || limit > MAX_CANDIDATES) {\n\t\tthrow new Error(\"Reconciliation candidate limit must be from 1 to 100\");\n\t}\n}\n\nfunction assertIdentity(input: { collectionId: string; runToken: string }): void {\n\tif (!input.collectionId || !input.runToken) {\n\t\tthrow new Error(\"Reconciliation requires an exact collection and run token\");\n\t}\n}\n\nfunction assertLeaseIdentity(input: {\n\tcollectionId: string;\n\trunToken: string;\n\tleaseToken: string;\n}): void {\n\tassertIdentity(input);\n\tif (!input.leaseToken) throw new Error(\"Reconciliation requires a lease token\");\n}\n\nfunction assertDuration(value: number, label: string, allowZero = false): void {\n\tif (\n\t\t!Number.isSafeInteger(value) ||\n\t\tvalue < (allowZero ? 0 : 1) ||\n\t\tvalue > MAX_PORTABLE_DURATION_SECONDS\n\t) {\n\t\tthrow new Error(`Reconciliation ${label} is outside the portable range`);\n\t}\n}\n\nfunction liveLease(db: Kysely<Database>, column = \"lease_expires_at\"): RawBuilder<boolean> {\n\tconst expiry = sql.ref(column);\n\treturn isPostgres(db)\n\t\t? sql<boolean>`${expiry} > to_char(statement_timestamp() AT TIME ZONE 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS.MS\"Z\"')`\n\t\t: sql<boolean>`${expiry} > strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;\n}\n\nfunction timestampIsDue(db: Kysely<Database>, column: string): RawBuilder<boolean> {\n\tconst value = sql.ref(column);\n\treturn isPostgres(db)\n\t\t? sql<boolean>`${value} <= to_char(statement_timestamp() AT TIME ZONE 'UTC', 'YYYY-MM-DD\"T\"HH24:MI:SS.MS\"Z\"')`\n\t\t: sql<boolean>`${value} <= strftime('%Y-%m-%dT%H:%M:%fZ', 'now')`;\n}\n\nfunction timestampOffset(db: Kysely<Database>, offsetSeconds: number): RawBuilder<string> {\n\tif (isPostgres(db)) {\n\t\treturn sql<string>`to_char(\n\t\t\t(clock_timestamp() AT TIME ZONE 'UTC') + (${offsetSeconds} * INTERVAL '1 second'),\n\t\t\t'YYYY-MM-DD\"T\"HH24:MI:SS.MS\"Z\"'\n\t\t)`;\n\t}\n\treturn sql<string>`strftime(\n\t\t'%Y-%m-%dT%H:%M:%fZ',\n\t\t'now',\n\t\t${`${offsetSeconds >= 0 ? \"+\" : \"\"}${offsetSeconds} seconds`}\n\t)`;\n}\n\nfunction contentTableName(collectionSlug: string): string {\n\tvalidateIdentifier(collectionSlug, \"collection slug\");\n\tconst tableName = `ec_${collectionSlug}`;\n\tvalidateIdentifier(tableName, \"content table\");\n\treturn tableName;\n}\n","import type { Kysely } from \"kysely\";\n\nimport { MediaUsageWorkRepository } from \"../../database/repositories/media-usage-work.js\";\nimport type { Database } from \"../../database/types.js\";\nimport {\n\tbuildContentMediaUsageFieldFingerprint,\n\tloadContentMediaUsageFields,\n} from \"./content-fields.js\";\nimport {\n\tMediaUsageReconciliationRepository,\n\ttype MediaUsageReconciliationClaim,\n\ttype MediaUsageReconciliationRecord,\n} from \"./reconciliation.js\";\nimport { CONTENT_SOURCE_SCHEMA_VERSION } from \"./types.js\";\n\nexport const MEDIA_USAGE_RECONCILIATION_LIMITS = Object.freeze({\n\tcandidatesPerTick: 4,\n\tpageSize: 50,\n\tleaseDurationSeconds: 60,\n\tmaxAttempts: 5,\n\tretryBaseSeconds: 30,\n\tretryMaxSeconds: 15 * 60,\n\tretryJitterRatio: 0.25,\n\tmaxQueriesPerTick: 20,\n});\n\nexport type MediaUsageReconciliationOutcome =\n\t| \"inactive\"\n\t| \"not_due\"\n\t| \"claim_lost\"\n\t| \"advanced\"\n\t| \"deferred\"\n\t| \"completed\"\n\t| \"retry\"\n\t| \"failed\";\n\nexport type MediaUsageReconciliationScanOutcome =\n\t| \"advanced\"\n\t| \"exhausted\"\n\t| \"deferred\"\n\t| \"restart_required\";\n\nexport async function processDueMediaUsageReconciliation(\n\tdb: Kysely<Database>,\n): Promise<MediaUsageReconciliationOutcome> {\n\tconst activation = await db\n\t\t.selectFrom(\"_emdash_media_usage_activation\")\n\t\t.select(\"state\")\n\t\t.where(\"task_key\", \"=\", \"incremental_capture\")\n\t\t.executeTakeFirst();\n\tif (activation?.state !== \"active\") return \"inactive\";\n\n\tconst reconciliation = new MediaUsageReconciliationRepository(db);\n\tif (await reconciliation.deleteOneObsolete()) return \"completed\";\n\tconst [failed] = await reconciliation.findFailed(1);\n\tif (failed) {\n\t\tif (await reconciliation.finishFailedCoverage(failed.collectionId, failed.runToken)) {\n\t\t\treturn \"failed\";\n\t\t}\n\t\tif (await reconciliation.resetFailedForNewEpoch(failed)) return \"advanced\";\n\t}\n\n\tawait reconciliation.seedNextCandidate();\n\tconst candidates = await reconciliation.findDue(\n\t\tMEDIA_USAGE_RECONCILIATION_LIMITS.candidatesPerTick,\n\t);\n\tlet claim: MediaUsageReconciliationClaim | null = null;\n\tfor (const candidate of candidates) {\n\t\tclaim = await reconciliation.claim({\n\t\t\tcollectionId: candidate.collectionId,\n\t\t\trunToken: candidate.runToken,\n\t\t\tleaseDurationSeconds: MEDIA_USAGE_RECONCILIATION_LIMITS.leaseDurationSeconds,\n\t\t});\n\t\tif (claim) break;\n\t}\n\tif (!claim) return candidates.length === 0 ? \"not_due\" : \"claim_lost\";\n\n\ttry {\n\t\treturn await processClaimedReconciliation(db, claim);\n\t} catch (error) {\n\t\tconst terminal = claim.attemptCount + 1 >= MEDIA_USAGE_RECONCILIATION_LIMITS.maxAttempts;\n\t\tconst recorded = await reconciliation.recordFailure({\n\t\t\tcollectionId: claim.collectionId,\n\t\t\trunToken: claim.runToken,\n\t\t\tleaseToken: claim.leaseToken,\n\t\t\terrorCode: \"MEDIA_USAGE_RECONCILIATION_FAILED\",\n\t\t\tretryDelaySeconds: retryDelaySeconds(claim.attemptCount),\n\t\t\tterminal,\n\t\t});\n\t\tif (!recorded) return \"claim_lost\";\n\t\tif (terminal) await reconciliation.finishFailedCoverage(claim.collectionId, claim.runToken);\n\t\tconsole.error(\"[media-usage:reconciliation] Processing failed:\", error);\n\t\treturn terminal ? \"failed\" : \"retry\";\n\t}\n}\n\nexport async function processClaimedMediaUsageReconciliationScan(\n\tdb: Kysely<Database>,\n\tclaim: MediaUsageReconciliationClaim,\n\toptions: { releaseOnExhausted?: boolean } = {},\n): Promise<MediaUsageReconciliationScanOutcome> {\n\tconst reconciliation = new MediaUsageReconciliationRepository(db);\n\tlet current = await reconciliation.findByIdentity(claim.collectionId, claim.runToken);\n\tif (!current || current.leaseToken !== claim.leaseToken || current.phase !== \"scan\") {\n\t\treturn \"deferred\";\n\t}\n\n\tlet fields;\n\tlet fieldFingerprint: string;\n\tif (current.targetEpoch === null) {\n\t\tconst targetEpoch = await reconciliation.beginRun(claim);\n\t\tif (targetEpoch === null) {\n\t\t\tawait reconciliation.release({ ...claim, delaySeconds: 30 });\n\t\t\treturn \"deferred\";\n\t\t}\n\t\tfields = await loadContentMediaUsageFields(db, claim.collectionSlug, claim.collectionId);\n\t\tfieldFingerprint = await buildContentMediaUsageFieldFingerprint(fields);\n\t\tconst scanUpperId =\n\t\t\tfields.extractionFields.length === 0 ? null : await reconciliation.findScanUpperId(claim);\n\t\tif (\n\t\t\t!(await reconciliation.initializeScan({\n\t\t\t\tclaim,\n\t\t\t\ttargetEpoch,\n\t\t\t\tfieldFingerprint,\n\t\t\t\tscanUpperId,\n\t\t\t}))\n\t\t) {\n\t\t\treturn \"deferred\";\n\t\t}\n\t\tcurrent = await reconciliation.findByIdentity(claim.collectionId, claim.runToken);\n\t\tif (!current) return \"deferred\";\n\t} else {\n\t\tfields = await loadContentMediaUsageFields(db, claim.collectionSlug, claim.collectionId);\n\t\tfieldFingerprint = await buildContentMediaUsageFieldFingerprint(fields);\n\t}\n\n\tif (current.fieldFingerprint !== fieldFingerprint || current.targetEpoch === null) {\n\t\treturn \"restart_required\";\n\t}\n\tconst contentIds = await reconciliation.findScanPage(current, 50);\n\tif (contentIds.length === 0) {\n\t\tif (options.releaseOnExhausted ?? true) {\n\t\t\tawait reconciliation.release({ ...claim, delaySeconds: 30 });\n\t\t}\n\t\treturn \"exhausted\";\n\t}\n\n\tconst work = new MediaUsageWorkRepository(db);\n\tawait work.enqueueReconciliationPage({\n\t\tcollectionId: claim.collectionId,\n\t\tcollectionSlug: claim.collectionSlug,\n\t\trunToken: claim.runToken,\n\t\tleaseToken: claim.leaseToken,\n\t\tchangeEpoch: current.targetEpoch,\n\t\tphase: \"scan\",\n\t\tcontentIds,\n\t});\n\tconst nextCursor = contentIds.at(-1)!;\n\tif (\n\t\t!(await reconciliation.checkpointScan({\n\t\t\tclaim,\n\t\t\ttargetEpoch: current.targetEpoch,\n\t\t\tpreviousCursor: current.scanCursor,\n\t\t\tnextCursor,\n\t\t}))\n\t) {\n\t\treturn \"deferred\";\n\t}\n\tif (!(await reconciliation.release({ ...claim, delaySeconds: 0 }))) return \"deferred\";\n\treturn \"advanced\";\n}\n\nasync function processClaimedReconciliation(\n\tdb: Kysely<Database>,\n\tclaim: MediaUsageReconciliationClaim,\n): Promise<MediaUsageReconciliationOutcome> {\n\tconst reconciliation = new MediaUsageReconciliationRepository(db);\n\tlet current = await reconciliation.findByIdentity(claim.collectionId, claim.runToken);\n\tif (!current || current.leaseToken !== claim.leaseToken) return \"claim_lost\";\n\tif (current.targetEpoch !== null && !(await reconciliation.ownsRun(claim, current.targetEpoch))) {\n\t\treturn restartReconciliation(db, reconciliation, claim, current);\n\t}\n\n\tif (current.phase === \"scan\") {\n\t\tconst outcome = await processClaimedMediaUsageReconciliationScan(db, claim, {\n\t\t\treleaseOnExhausted: false,\n\t\t});\n\t\tif (outcome === \"restart_required\") {\n\t\t\tcurrent =\n\t\t\t\t(await reconciliation.findByIdentity(claim.collectionId, claim.runToken)) ?? current;\n\t\t\treturn restartReconciliation(db, reconciliation, claim, current);\n\t\t}\n\t\tif (outcome !== \"exhausted\") return outcome;\n\t\tcurrent = (await reconciliation.findByIdentity(claim.collectionId, claim.runToken)) ?? current;\n\t\tconst barrier = await reconciliation.findWorkBarrier(claim.collectionId);\n\t\tif (barrier.state === \"failed\") {\n\t\t\treturn failReconciliation(reconciliation, claim, barrier.errorCode, true);\n\t\t}\n\t\tif (barrier.state === \"pending\") {\n\t\t\tawait reconciliation.release({ ...claim, delaySeconds: 30 });\n\t\t\treturn \"deferred\";\n\t\t}\n\t\tif (current.targetEpoch === null || current.fieldFingerprint === null) return \"claim_lost\";\n\t\tconst sourceUpperKey = await reconciliation.findSourceUpperKey(claim, current.targetEpoch);\n\t\tif (\n\t\t\t!(await reconciliation.transitionToSources({\n\t\t\t\tclaim,\n\t\t\t\ttargetEpoch: current.targetEpoch,\n\t\t\t\tfieldFingerprint: current.fieldFingerprint,\n\t\t\t\tsourceUpperKey,\n\t\t\t}))\n\t\t) {\n\t\t\treturn \"claim_lost\";\n\t\t}\n\t\tif (!(await reconciliation.release({ ...claim, delaySeconds: 0 }))) return \"claim_lost\";\n\t\treturn \"advanced\";\n\t}\n\n\treturn processSourcePhase(db, reconciliation, claim, current);\n}\n\nasync function processSourcePhase(\n\tdb: Kysely<Database>,\n\treconciliation: MediaUsageReconciliationRepository,\n\tclaim: MediaUsageReconciliationClaim,\n\tcurrent: MediaUsageReconciliationRecord,\n): Promise<MediaUsageReconciliationOutcome> {\n\tif (current.targetEpoch === null || current.fieldFingerprint === null) return \"claim_lost\";\n\tconst fields = await loadContentMediaUsageFields(db, claim.collectionSlug, claim.collectionId);\n\tconst fieldFingerprint = await buildContentMediaUsageFieldFingerprint(fields);\n\tif (fieldFingerprint !== current.fieldFingerprint) {\n\t\treturn restartReconciliation(db, reconciliation, claim, current, fields, fieldFingerprint);\n\t}\n\n\tconst page = await reconciliation.findSourcePage(\n\t\tcurrent,\n\t\tMEDIA_USAGE_RECONCILIATION_LIMITS.pageSize,\n\t);\n\tif (page.length > 0) {\n\t\tconst malformed = page.some(\n\t\t\t(source) =>\n\t\t\t\t!source.contentId ||\n\t\t\t\t(source.sourceVariant !== \"columns\" && source.sourceVariant !== \"draft_overlay\"),\n\t\t);\n\t\tif (malformed) {\n\t\t\treturn failReconciliation(\n\t\t\t\treconciliation,\n\t\t\t\tclaim,\n\t\t\t\t\"MEDIA_USAGE_RECONCILIATION_INVALID_SOURCE\",\n\t\t\t\tfalse,\n\t\t\t);\n\t\t}\n\t\tconst contentIds = [...new Set(page.map((source) => source.contentId!))];\n\t\tconst enqueueIds =\n\t\t\tfields.extractionFields.length === 0\n\t\t\t\t? contentIds\n\t\t\t\t: await reconciliation.findMissingContentIds(claim.collectionSlug, contentIds);\n\t\tif (enqueueIds.length > 0) {\n\t\t\tawait new MediaUsageWorkRepository(db).enqueueReconciliationPage({\n\t\t\t\tcollectionId: claim.collectionId,\n\t\t\t\tcollectionSlug: claim.collectionSlug,\n\t\t\t\trunToken: claim.runToken,\n\t\t\t\tleaseToken: claim.leaseToken,\n\t\t\t\tchangeEpoch: current.targetEpoch,\n\t\t\t\tphase: \"sources\",\n\t\t\t\tcontentIds: enqueueIds,\n\t\t\t});\n\t\t}\n\t\tif (\n\t\t\t!(await reconciliation.checkpointSources({\n\t\t\t\tclaim,\n\t\t\t\ttargetEpoch: current.targetEpoch,\n\t\t\t\tpreviousCursor: current.sourceCursor,\n\t\t\t\tnextCursor: page.at(-1)!.sourceKey,\n\t\t\t}))\n\t\t) {\n\t\t\treturn \"claim_lost\";\n\t\t}\n\t\tif (!(await reconciliation.release({ ...claim, delaySeconds: 0 }))) return \"claim_lost\";\n\t\treturn \"advanced\";\n\t}\n\n\tconst barrier = await reconciliation.findWorkBarrier(claim.collectionId);\n\tif (barrier.state === \"failed\") {\n\t\treturn failReconciliation(reconciliation, claim, barrier.errorCode, true);\n\t}\n\tif (barrier.state === \"pending\") {\n\t\tawait reconciliation.release({ ...claim, delaySeconds: 30 });\n\t\treturn \"deferred\";\n\t}\n\tif (\n\t\t!(await reconciliation.finalizeCoverage({\n\t\t\tclaim,\n\t\t\ttargetEpoch: current.targetEpoch,\n\t\t\tfieldFingerprint,\n\t\t\tschemaVersion: CONTENT_SOURCE_SCHEMA_VERSION,\n\t\t}))\n\t) {\n\t\treturn \"claim_lost\";\n\t}\n\tif (!(await reconciliation.deleteFinalized(claim))) return \"claim_lost\";\n\treturn \"completed\";\n}\n\nasync function restartReconciliation(\n\tdb: Kysely<Database>,\n\treconciliation: MediaUsageReconciliationRepository,\n\tclaim: MediaUsageReconciliationClaim,\n\tcurrent: MediaUsageReconciliationRecord,\n\tfields?: Awaited<ReturnType<typeof loadContentMediaUsageFields>>,\n\tfieldFingerprint?: string,\n): Promise<MediaUsageReconciliationOutcome> {\n\tif (current.targetEpoch === null) return \"claim_lost\";\n\tconst discoveredFields =\n\t\tfields ?? (await loadContentMediaUsageFields(db, claim.collectionSlug, claim.collectionId));\n\tconst fingerprint =\n\t\tfieldFingerprint ?? (await buildContentMediaUsageFieldFingerprint(discoveredFields));\n\tconst targetEpoch = await reconciliation.restartRun(claim, current.targetEpoch);\n\tif (targetEpoch === null) {\n\t\tawait reconciliation.release({ ...claim, delaySeconds: 30 });\n\t\treturn \"deferred\";\n\t}\n\tconst scanUpperId =\n\t\tdiscoveredFields.extractionFields.length === 0\n\t\t\t? null\n\t\t\t: await reconciliation.findScanUpperId(claim);\n\tif (\n\t\t!(await reconciliation.restartScan({\n\t\t\tclaim,\n\t\t\tpreviousEpoch: current.targetEpoch,\n\t\t\ttargetEpoch,\n\t\t\tfieldFingerprint: fingerprint,\n\t\t\tscanUpperId,\n\t\t}))\n\t) {\n\t\treturn \"claim_lost\";\n\t}\n\tif (!(await reconciliation.release({ ...claim, delaySeconds: 0 }))) return \"claim_lost\";\n\treturn \"advanced\";\n}\n\nasync function failReconciliation(\n\treconciliation: MediaUsageReconciliationRepository,\n\tclaim: MediaUsageReconciliationClaim,\n\terrorCode: string,\n\tentryFailure: boolean,\n): Promise<MediaUsageReconciliationOutcome> {\n\tconst recorded = entryFailure\n\t\t? await reconciliation.recordEntryFailure(claim)\n\t\t: await reconciliation.recordFailure({\n\t\t\t\tcollectionId: claim.collectionId,\n\t\t\t\trunToken: claim.runToken,\n\t\t\t\tleaseToken: claim.leaseToken,\n\t\t\t\terrorCode,\n\t\t\t\tretryDelaySeconds: 0,\n\t\t\t\tterminal: true,\n\t\t\t});\n\tif (!recorded) return \"claim_lost\";\n\tawait reconciliation.finishFailedCoverage(claim.collectionId, claim.runToken);\n\treturn \"failed\";\n}\n\nfunction retryDelaySeconds(attemptCount: number): number {\n\tconst exponential = Math.min(\n\t\tMEDIA_USAGE_RECONCILIATION_LIMITS.retryMaxSeconds,\n\t\tMEDIA_USAGE_RECONCILIATION_LIMITS.retryBaseSeconds * 2 ** attemptCount,\n\t);\n\tconst jitter = Math.floor(\n\t\texponential * MEDIA_USAGE_RECONCILIATION_LIMITS.retryJitterRatio * Math.random(),\n\t);\n\treturn Math.min(MEDIA_USAGE_RECONCILIATION_LIMITS.retryMaxSeconds, exponential + jitter);\n}\n","import type { Kysely } from \"kysely\";\n\nimport {\n\tMediaUsageWorkRepository,\n\ttype MediaUsageWorkRecord,\n} from \"../../database/repositories/media-usage-work.js\";\nimport { MediaUsageRepository } from \"../../database/repositories/media-usage.js\";\nimport type { Database } from \"../../database/types.js\";\nimport {\n\trefreshContentMediaUsageForWork,\n\ttype ContentMediaUsageRefreshErrorCode,\n} from \"./content-refresh.js\";\n\nexport const MEDIA_USAGE_WORK_PROCESSING_LIMITS = Object.freeze({\n\tcandidatesPerTick: 4,\n\tjobsPerTick: 1,\n\tmaxTickDurationMs: 5_000,\n\tleaseDurationSeconds: 60,\n\tmaxAttempts: 5,\n\tretryBaseSeconds: 30,\n\tretryMaxSeconds: 15 * 60,\n\tretryJitterRatio: 0.25,\n\tordinaryStatementsPerJob: 20,\n});\n\nexport type MediaUsageWorkProcessingOutcome =\n\t| \"inactive\"\n\t| \"not_due\"\n\t| \"claim_lost\"\n\t| \"completed\"\n\t| \"retry\"\n\t| \"failed\"\n\t| \"superseded\"\n\t| \"obsolete\";\n\nexport interface MediaUsageWorkProcessingResult {\n\toutcome: MediaUsageWorkProcessingOutcome;\n\tclaimed: boolean;\n}\n\nexport interface MediaUsageWorkTickResult {\n\tcandidateCount: number;\n\tclaimedCount: number;\n\tcompletedCount: number;\n\tretryCount: number;\n\tfailedCount: number;\n\tsupersededCount: number;\n\tobsoleteCount: number;\n\tdurationMs: number;\n\tadmissionClosed: boolean;\n}\n\nexport async function processMediaUsageWorkAfterWrite(\n\tdb: Kysely<Database>,\n\tcollectionSlug: string,\n\tcontentId: string,\n): Promise<MediaUsageWorkProcessingResult> {\n\tif (!(await isIncrementalCaptureActive(db))) {\n\t\treturn { outcome: \"inactive\", claimed: false };\n\t}\n\n\tconst repo = new MediaUsageWorkRepository(db);\n\tconst work = await repo.findWorkForContent(collectionSlug, contentId);\n\tif (!work) return { outcome: \"not_due\", claimed: false };\n\treturn processCandidate(db, repo, work);\n}\n\nexport async function processDueMediaUsageWork(\n\tdb: Kysely<Database>,\n): Promise<MediaUsageWorkTickResult> {\n\tconst startedAt = Date.now();\n\tconst result: MediaUsageWorkTickResult = {\n\t\tcandidateCount: 0,\n\t\tclaimedCount: 0,\n\t\tcompletedCount: 0,\n\t\tretryCount: 0,\n\t\tfailedCount: 0,\n\t\tsupersededCount: 0,\n\t\tobsoleteCount: 0,\n\t\tdurationMs: 0,\n\t\tadmissionClosed: false,\n\t};\n\n\tif (!(await isIncrementalCaptureActive(db))) {\n\t\tresult.durationMs = Date.now() - startedAt;\n\t\treturn result;\n\t}\n\n\tconst repo = new MediaUsageWorkRepository(db);\n\tconst candidates = await repo.findDueWork(MEDIA_USAGE_WORK_PROCESSING_LIMITS.candidatesPerTick);\n\tresult.candidateCount = candidates.length;\n\n\tfor (const candidate of candidates) {\n\t\tif (\n\t\t\tresult.claimedCount >= MEDIA_USAGE_WORK_PROCESSING_LIMITS.jobsPerTick ||\n\t\t\tDate.now() - startedAt >= MEDIA_USAGE_WORK_PROCESSING_LIMITS.maxTickDurationMs\n\t\t) {\n\t\t\tresult.admissionClosed = true;\n\t\t\tbreak;\n\t\t}\n\n\t\tconst processed = await processCandidate(db, repo, candidate);\n\t\tif (processed.claimed) result.claimedCount++;\n\t\tif (processed.outcome === \"completed\") result.completedCount++;\n\t\tif (processed.outcome === \"retry\") result.retryCount++;\n\t\tif (processed.outcome === \"failed\") result.failedCount++;\n\t\tif (processed.outcome === \"superseded\") result.supersededCount++;\n\t\tif (processed.outcome === \"obsolete\") result.obsoleteCount++;\n\t}\n\n\tresult.durationMs = Date.now() - startedAt;\n\treturn result;\n}\n\nasync function processCandidate(\n\tdb: Kysely<Database>,\n\trepo: MediaUsageWorkRepository,\n\tcandidate: MediaUsageWorkRecord,\n): Promise<MediaUsageWorkProcessingResult> {\n\tconst claimed = await repo.claimWork({\n\t\tcollectionId: candidate.collectionId,\n\t\tcontentId: candidate.contentId,\n\t\tworkVersion: candidate.workVersion,\n\t\tleaseDurationSeconds: MEDIA_USAGE_WORK_PROCESSING_LIMITS.leaseDurationSeconds,\n\t});\n\tif (!claimed?.leaseToken) return { outcome: \"claim_lost\", claimed: false };\n\n\tconst lease = {\n\t\tcollectionId: claimed.collectionId,\n\t\tcontentId: claimed.contentId,\n\t\tworkVersion: claimed.workVersion,\n\t\tleaseToken: claimed.leaseToken,\n\t};\n\tif (!(await collectionIdentityIsCurrent(db, claimed.collectionId, claimed.collectionSlug))) {\n\t\treturn {\n\t\t\toutcome: (await repo.completeWork(lease)) ? \"obsolete\" : \"superseded\",\n\t\t\tclaimed: true,\n\t\t};\n\t}\n\n\tconst refresh = await refreshContentMediaUsageForWork(\n\t\tdb,\n\t\tclaimed.collectionId,\n\t\tclaimed.collectionSlug,\n\t\tclaimed.contentId,\n\t);\n\tif (refresh.success) {\n\t\tconst completed = await repo.completeWork(lease);\n\t\tif (completed) {\n\t\t\tawait new MediaUsageRepository(db).recordIncrementalSuccess({\n\t\t\t\tcollectionId: claimed.collectionId,\n\t\t\t\tcollectionSlug: claimed.collectionSlug,\n\t\t\t});\n\t\t}\n\t\treturn {\n\t\t\toutcome: completed ? \"completed\" : \"superseded\",\n\t\t\tclaimed: true,\n\t\t};\n\t}\n\n\tif (!(await collectionIdentityIsCurrent(db, claimed.collectionId, claimed.collectionSlug))) {\n\t\treturn {\n\t\t\toutcome: (await repo.completeWork(lease)) ? \"obsolete\" : \"superseded\",\n\t\t\tclaimed: true,\n\t\t};\n\t}\n\n\tconst errorCode = processingErrorCode(refresh.errorCode);\n\tconst terminal =\n\t\terrorCode === \"MEDIA_USAGE_RESOURCE_LIMIT\" ||\n\t\tclaimed.attemptCount + 1 >= MEDIA_USAGE_WORK_PROCESSING_LIMITS.maxAttempts;\n\tif (terminal) {\n\t\tconst failed = await repo.failWork({ ...lease, errorCode });\n\t\tif (failed) {\n\t\t\tawait new MediaUsageRepository(db).recordIncrementalFailure({\n\t\t\t\tcollectionId: claimed.collectionId,\n\t\t\t\tcollectionSlug: claimed.collectionSlug,\n\t\t\t\tcontentId: claimed.contentId,\n\t\t\t\tworkVersion: claimed.workVersion,\n\t\t\t\terrorCode,\n\t\t\t});\n\t\t}\n\t\treturn {\n\t\t\toutcome: failed ? \"failed\" : \"superseded\",\n\t\t\tclaimed: true,\n\t\t};\n\t}\n\n\treturn {\n\t\toutcome: (await repo.retryWork({\n\t\t\t...lease,\n\t\t\terrorCode,\n\t\t\tretryDelaySeconds: retryDelaySeconds(claimed.attemptCount),\n\t\t}))\n\t\t\t? \"retry\"\n\t\t\t: \"superseded\",\n\t\tclaimed: true,\n\t};\n}\n\nasync function isIncrementalCaptureActive(db: Kysely<Database>): Promise<boolean> {\n\tconst row = await db\n\t\t.selectFrom(\"_emdash_media_usage_activation\")\n\t\t.select(\"state\")\n\t\t.where(\"task_key\", \"=\", \"incremental_capture\")\n\t\t.executeTakeFirst();\n\treturn row?.state === \"active\";\n}\n\nasync function collectionIdentityIsCurrent(\n\tdb: Kysely<Database>,\n\tcollectionId: string,\n\tcollectionSlug: string,\n): Promise<boolean> {\n\tconst row = await db\n\t\t.selectFrom(\"_emdash_collections\")\n\t\t.select(\"id\")\n\t\t.where(\"id\", \"=\", collectionId)\n\t\t.where(\"slug\", \"=\", collectionSlug)\n\t\t.executeTakeFirst();\n\treturn row !== undefined;\n}\n\nfunction retryDelaySeconds(attemptCount: number): number {\n\tconst exponential = Math.min(\n\t\tMEDIA_USAGE_WORK_PROCESSING_LIMITS.retryMaxSeconds,\n\t\tMEDIA_USAGE_WORK_PROCESSING_LIMITS.retryBaseSeconds * 2 ** attemptCount,\n\t);\n\tconst jitter = Math.floor(\n\t\texponential * MEDIA_USAGE_WORK_PROCESSING_LIMITS.retryJitterRatio * Math.random(),\n\t);\n\treturn Math.min(MEDIA_USAGE_WORK_PROCESSING_LIMITS.retryMaxSeconds, exponential + jitter);\n}\n\nfunction processingErrorCode(errorCode: ContentMediaUsageRefreshErrorCode | undefined): string {\n\tif (\n\t\terrorCode === \"DRAFT_REVISION_NOT_FOUND\" ||\n\t\terrorCode === \"DRAFT_REVISION_MISMATCH\" ||\n\t\terrorCode === \"DRAFT_REVISION_INVALID\"\n\t) {\n\t\treturn \"MEDIA_USAGE_SNAPSHOT_FAILED\";\n\t}\n\tif (errorCode === \"CONTENT_USAGE_GENERATION_CONFLICT\") {\n\t\treturn \"MEDIA_USAGE_GENERATION_CONFLICT\";\n\t}\n\tif (errorCode === \"CONTENT_USAGE_RESOURCE_LIMIT\") return \"MEDIA_USAGE_RESOURCE_LIMIT\";\n\treturn \"MEDIA_USAGE_PROCESSING_FAILED\";\n}\n","import { createSiteInfo, type SiteInfoOptions } from \"../context.js\";\nimport type { SandboxOptions } from \"./types.js\";\n\n/**\n * Build platform sandbox options with the same normalized site context used\n * by trusted plugin hooks and routes.\n */\nexport function createSandboxRunnerOptions(\n\toptions: Omit<SandboxOptions, \"siteInfo\">,\n\tsiteInfo?: SiteInfoOptions,\n): SandboxOptions {\n\treturn {\n\t\t...options,\n\t\tsiteInfo: createSiteInfo(siteInfo ?? {}),\n\t};\n}\n","import type { Kysely } from \"kysely\";\nimport { ulid } from \"ulidx\";\n\nimport {\n\tMediaUsageRepository,\n\ttype MediaUsageCleanupCandidate,\n\ttype MediaUsageCleanupCursor,\n} from \"../../database/repositories/media-usage.js\";\nimport type { Database } from \"../../database/types.js\";\n\nexport const MEDIA_USAGE_CLEANUP_CANDIDATE_LIMIT = 250;\nexport const MEDIA_USAGE_CLEANUP_DELETE_LIMIT = 50;\nexport const MEDIA_USAGE_CLEANUP_WRITE_LEASE_DELETE_LIMIT = 49;\nexport const MEDIA_USAGE_CLEANUP_INTERVAL_MS = 60 * 1000;\nexport const MEDIA_USAGE_CLEANUP_LEASE_MS = 5 * 60 * 1000;\nexport const MEDIA_USAGE_CLEANUP_SAFETY_WINDOW_MS = 60 * 60 * 1000;\nconst MEDIA_USAGE_CLEANUP_TIME_BUDGET_MS = 5 * 1000;\n\nexport interface MediaUsageCleanupResult {\n\tstatus: \"completed\" | \"failed\" | \"skipped\";\n\tcandidateRows: number;\n\tdeletedRows: number;\n\tdeletedOrphans: number;\n\tdeletedStale: number;\n\tdeletedAbandoned: number;\n\tdeletedWriteLeases: number;\n\tbacklogLowerBound: number;\n\tscanHasMore: boolean;\n\tdurationMs: number;\n}\n\ninterface CleanupCandidates {\n\torphanIds: string[];\n\tstaleIds: string[];\n\tabandonedIds: string[];\n\tentries: CleanupCandidateEntry[];\n}\n\ntype CleanupTarget = \"orphan\" | \"stale\" | \"abandoned\";\n\ninterface CleanupCandidateEntry {\n\tcandidate: MediaUsageCleanupCandidate;\n\ttarget: CleanupTarget | null;\n}\n\n/**\n * Reclaims a bounded window of obsolete media-usage occurrences.\n *\n * The persisted claim makes a cron tick single-flight across Worker isolates\n * and Node processes.\n */\nexport async function cleanupMediaUsage(db: Kysely<Database>): Promise<MediaUsageCleanupResult> {\n\tconst startedMs = Date.now();\n\tconst canIssueStatement = () => withinBudget(startedMs);\n\tconst repo = new MediaUsageRepository(db);\n\tconst leaseToken = ulid();\n\tconst claim = await repo.claimMediaUsageCleanup({\n\t\tleaseToken,\n\t\tleaseDurationSeconds: MEDIA_USAGE_CLEANUP_LEASE_MS / 1000,\n\t\tnextEligibleDelaySeconds: MEDIA_USAGE_CLEANUP_INTERVAL_MS / 1000,\n\t\tsweepSafetyWindowSeconds: MEDIA_USAGE_CLEANUP_SAFETY_WINDOW_MS / 1000,\n\t});\n\tif (!claim) return emptyResult(\"skipped\", elapsedSince(startedMs));\n\n\tlet candidateRows = 0;\n\tlet deletedOrphans = 0;\n\tlet deletedStale = 0;\n\tlet deletedAbandoned = 0;\n\tlet deletedWriteLeases = 0;\n\tlet backlogLowerBound = 0;\n\tlet scanHasMore = false;\n\tlet nextCursor = claim.cursor;\n\tlet sweepComplete = false;\n\n\ttry {\n\t\tif (canIssueStatement()) {\n\t\t\tdeletedWriteLeases = await repo.deleteExpiredGenerationWriteLeases(\n\t\t\t\tMEDIA_USAGE_CLEANUP_WRITE_LEASE_DELETE_LIMIT,\n\t\t\t\tcleanupLease(leaseToken),\n\t\t\t\tcanIssueStatement,\n\t\t\t);\n\t\t}\n\n\t\tif (canIssueStatement()) {\n\t\t\tconst cutoff = claim.scanBeforeAt;\n\t\t\tconst candidates = await repo.findMediaUsageCleanupCandidates({\n\t\t\t\tcutoff,\n\t\t\t\tcursor: claim.cursor,\n\t\t\t\tlimit: MEDIA_USAGE_CLEANUP_CANDIDATE_LIMIT,\n\t\t\t\tcleanupLease: cleanupLease(leaseToken),\n\t\t\t});\n\t\t\tcandidateRows = candidates.length;\n\t\t\tscanHasMore = candidates.length === MEDIA_USAGE_CLEANUP_CANDIDATE_LIMIT;\n\n\t\t\tconst selected = selectCleanupCandidates(candidates, claim.claimedAt);\n\t\t\tbacklogLowerBound =\n\t\t\t\tselected.orphanIds.length + selected.staleIds.length + selected.abandonedIds.length;\n\t\t\tconst completedTargets = new Set<CleanupTarget>();\n\t\t\tlet canContinue = true;\n\n\t\t\tif (canIssueStatement() && selected.orphanIds.length > 0) {\n\t\t\t\tdeletedOrphans = await repo.deleteOrphanOccurrencesOlderThan(\n\t\t\t\t\tcutoff,\n\t\t\t\t\tselected.orphanIds.length,\n\t\t\t\t\t{\n\t\t\t\t\t\tcandidateIds: selected.orphanIds,\n\t\t\t\t\t\tcleanupLease: cleanupLease(leaseToken),\n\t\t\t\t\t\tcanIssueStatement,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tcanContinue = deletedOrphans === selected.orphanIds.length;\n\t\t\t\tif (canContinue) completedTargets.add(\"orphan\");\n\t\t\t}\n\t\t\tif (canContinue && canIssueStatement() && selected.staleIds.length > 0) {\n\t\t\t\tdeletedStale = await repo.deleteStaleGenerationsOlderThan(\n\t\t\t\t\tcutoff,\n\t\t\t\t\tselected.staleIds.length,\n\t\t\t\t\t{\n\t\t\t\t\t\tcandidateIds: selected.staleIds,\n\t\t\t\t\t\tcleanupLease: cleanupLease(leaseToken),\n\t\t\t\t\t\tcanIssueStatement,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tcanContinue = deletedStale === selected.staleIds.length;\n\t\t\t\tif (canContinue) completedTargets.add(\"stale\");\n\t\t\t}\n\t\t\tif (canContinue && canIssueStatement() && selected.abandonedIds.length > 0) {\n\t\t\t\tdeletedAbandoned = await repo.deleteAbandonedGenerationsOlderThan(\n\t\t\t\t\tcutoff,\n\t\t\t\t\tselected.abandonedIds.length,\n\t\t\t\t\t{\n\t\t\t\t\t\tcandidateIds: selected.abandonedIds,\n\t\t\t\t\t\tcleanupLease: cleanupLease(leaseToken),\n\t\t\t\t\t\tcanIssueStatement,\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t\tif (deletedAbandoned === selected.abandonedIds.length) {\n\t\t\t\t\tcompletedTargets.add(\"abandoned\");\n\t\t\t\t}\n\t\t\t}\n\t\t\tconst hasIncompleteTargets = selected.entries.some(\n\t\t\t\t(entry) => entry.target !== null && !completedTargets.has(entry.target),\n\t\t\t);\n\t\t\tsweepComplete =\n\t\t\t\t!scanHasMore && selected.entries.length === candidates.length && !hasIncompleteTargets;\n\t\t\tnextCursor = sweepComplete\n\t\t\t\t? null\n\t\t\t\t: cursorAfterCompletedCandidates(selected, completedTargets, claim.cursor);\n\t\t}\n\n\t\tconst durationMs = elapsedSince(startedMs);\n\t\tconst completed = await repo.completeMediaUsageCleanup({\n\t\t\tleaseToken,\n\t\t\tnextCursor,\n\t\t\tsweepComplete,\n\t\t\tcandidateCount: candidateRows,\n\t\t\tdeletedOrphans,\n\t\t\tdeletedStale,\n\t\t\tdeletedAbandoned,\n\t\t\tdeletedWriteLeases,\n\t\t\tbacklogLowerBound,\n\t\t\tscanHasMore,\n\t\t\tdurationMs,\n\t\t});\n\n\t\treturn {\n\t\t\tstatus: completed ? \"completed\" : \"skipped\",\n\t\t\tcandidateRows,\n\t\t\tdeletedRows: deletedOrphans + deletedStale + deletedAbandoned,\n\t\t\tdeletedOrphans,\n\t\t\tdeletedStale,\n\t\t\tdeletedAbandoned,\n\t\t\tdeletedWriteLeases,\n\t\t\tbacklogLowerBound,\n\t\t\tscanHasMore,\n\t\t\tdurationMs,\n\t\t};\n\t} catch (error) {\n\t\tconst durationMs = elapsedSince(startedMs);\n\t\tconst failures = Math.min(claim.consecutiveFailures + 1, 5);\n\t\ttry {\n\t\t\tawait repo.failMediaUsageCleanup({\n\t\t\t\tleaseToken,\n\t\t\t\tretryDelaySeconds: failureDelayMs(failures) / 1000,\n\t\t\t\tconsecutiveFailures: failures,\n\t\t\t\tdurationMs,\n\t\t\t\terrorCode: \"MEDIA_USAGE_CLEANUP_FAILED\",\n\t\t\t});\n\t\t} catch (failureError) {\n\t\t\tconsole.error(\"[media-usage-cleanup] Failed to record cleanup failure:\", failureError);\n\t\t}\n\t\tconsole.error(\"[media-usage-cleanup] Cleanup failed:\", error);\n\t\treturn {\n\t\t\t...emptyResult(\"failed\", durationMs),\n\t\t\tcandidateRows,\n\t\t\tdeletedRows: deletedOrphans + deletedStale + deletedAbandoned,\n\t\t\tdeletedOrphans,\n\t\t\tdeletedStale,\n\t\t\tdeletedAbandoned,\n\t\t\tdeletedWriteLeases,\n\t\t\tbacklogLowerBound,\n\t\t\tscanHasMore,\n\t\t};\n\t}\n}\n\nfunction selectCleanupCandidates(\n\tcandidates: readonly MediaUsageCleanupCandidate[],\n\tactiveLeaseAt: string,\n): CleanupCandidates {\n\tconst orphanIds: string[] = [];\n\tconst staleIds: string[] = [];\n\tconst abandonedIds: string[] = [];\n\tconst entries: CleanupCandidateEntry[] = [];\n\n\tfor (const candidate of candidates) {\n\t\tif (hasActiveWriteLease(candidate, activeLeaseAt)) {\n\t\t\tentries.push({ candidate, target: null });\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst target = cleanupTarget(candidate);\n\t\tif (target === null) {\n\t\t\tentries.push({ candidate, target: null });\n\t\t\tcontinue;\n\t\t}\n\t\tif (\n\t\t\torphanIds.length + staleIds.length + abandonedIds.length >=\n\t\t\tMEDIA_USAGE_CLEANUP_DELETE_LIMIT\n\t\t) {\n\t\t\tbreak;\n\t\t}\n\t\tif (target === \"orphan\") orphanIds.push(candidate.id);\n\t\tif (target === \"stale\") staleIds.push(candidate.id);\n\t\tif (target === \"abandoned\") abandonedIds.push(candidate.id);\n\t\tentries.push({ candidate, target });\n\t}\n\n\treturn { orphanIds, staleIds, abandonedIds, entries };\n}\n\nfunction cleanupTarget(candidate: MediaUsageCleanupCandidate): CleanupTarget | null {\n\tif (candidate.currentGeneration === null) return \"orphan\";\n\tif (candidate.currentGeneration === candidate.generation || candidate.indexedAt === null)\n\t\treturn null;\n\treturn candidate.createdAt < candidate.indexedAt ? \"stale\" : \"abandoned\";\n}\n\nfunction hasActiveWriteLease(\n\tcandidate: MediaUsageCleanupCandidate,\n\tactiveLeaseAt: string,\n): boolean {\n\treturn candidate.writeLeaseExpiresAt !== null && candidate.writeLeaseExpiresAt > activeLeaseAt;\n}\n\nfunction cursorFor(candidate: MediaUsageCleanupCandidate): MediaUsageCleanupCursor {\n\treturn { createdAt: candidate.createdAt, id: candidate.id };\n}\n\nfunction cursorAfterCompletedCandidates(\n\tselected: CleanupCandidates,\n\tcompletedTargets: ReadonlySet<CleanupTarget>,\n\tpriorCursor: MediaUsageCleanupCursor | null,\n): MediaUsageCleanupCursor | null {\n\tlet cursor = priorCursor;\n\tfor (const entry of selected.entries) {\n\t\tif (entry.target !== null && !completedTargets.has(entry.target)) break;\n\t\tcursor = cursorFor(entry.candidate);\n\t}\n\treturn cursor;\n}\n\nfunction emptyResult(\n\tstatus: Extract<MediaUsageCleanupResult[\"status\"], \"failed\" | \"skipped\">,\n\tdurationMs: number,\n): MediaUsageCleanupResult {\n\treturn {\n\t\tstatus,\n\t\tcandidateRows: 0,\n\t\tdeletedRows: 0,\n\t\tdeletedOrphans: 0,\n\t\tdeletedStale: 0,\n\t\tdeletedAbandoned: 0,\n\t\tdeletedWriteLeases: 0,\n\t\tbacklogLowerBound: 0,\n\t\tscanHasMore: false,\n\t\tdurationMs,\n\t};\n}\n\nfunction withinBudget(startedMs: number): boolean {\n\treturn elapsedSince(startedMs) < MEDIA_USAGE_CLEANUP_TIME_BUDGET_MS;\n}\n\nfunction elapsedSince(startedMs: number): number {\n\treturn Math.max(0, Date.now() - startedMs);\n}\n\nfunction failureDelayMs(consecutiveFailures: number): number {\n\treturn Math.min(2 ** (consecutiveFailures - 1), 15) * MEDIA_USAGE_CLEANUP_INTERVAL_MS;\n}\n\nfunction cleanupLease(leaseToken: string) {\n\treturn { leaseToken };\n}\n","/**\n * System cleanup\n *\n * Runs periodic maintenance tasks that prevent unbounded accumulation of\n * expired or stale data. Called from cron scheduler ticks and (for latency-\n * sensitive subsystems) inline during relevant requests.\n *\n * Each subsystem cleanup is independent and non-fatal -- if one fails, the\n * rest still run. Failures are logged but never surface to callers.\n */\n\nimport { createKyselyAdapter, type AuthTables } from \"@premium-cms/auth/adapters/kysely\";\nimport type { Kysely } from \"kysely\";\n\nimport { cleanupExpiredChallenges } from \"./auth/challenge-store.js\";\nimport { MediaRepository } from \"./database/repositories/media.js\";\nimport { RevisionRepository } from \"./database/repositories/revision.js\";\nimport type { Database } from \"./database/types.js\";\nimport { removeUploadAttempt } from \"./media/upload-attempts.js\";\nimport { cleanupMediaUsage } from \"./media/usage/cleanup.js\";\nimport type { Storage } from \"./storage/types.js\";\n\n/**\n * Result of a system cleanup run.\n * Each field is the number of rows deleted, or -1 if the cleanup failed.\n */\nexport interface CleanupResult {\n\tchallenges: number;\n\texpiredTokens: number;\n\tpendingUploads: number;\n\tpendingUploadFiles: number;\n\tuploadAttempts: number;\n\trevisionsPruned: number;\n\tmediaUsage: number;\n}\n\nconst REVISION_KEEP_COUNT = 50;\nconst REVISION_PRUNE_BATCH_SIZE = 10;\n\n/**\n * Run all system cleanup tasks.\n *\n * Safe to call frequently -- each subsystem tolerates repeated calls, and\n * repeated calls with nothing to clean are cheap.\n *\n * @param db - The database instance\n * @param storage - Optional storage backend for deleting orphaned files.\n *   When omitted, pending upload DB rows are still deleted but the\n *   corresponding files in object storage are not removed.\n */\nexport async function runSystemCleanup(\n\tdb: Kysely<Database>,\n\tstorage?: Storage,\n): Promise<CleanupResult> {\n\tconst result: CleanupResult = {\n\t\tchallenges: -1,\n\t\texpiredTokens: -1,\n\t\tpendingUploads: -1,\n\t\tpendingUploadFiles: -1,\n\t\tuploadAttempts: -1,\n\t\trevisionsPruned: -1,\n\t\tmediaUsage: -1,\n\t};\n\n\t// 1. Passkey challenges (expire after 60s, clean anything past 5 min)\n\ttry {\n\t\tresult.challenges = await cleanupExpiredChallenges(db);\n\t} catch (error) {\n\t\tconsole.error(\"[cleanup] Failed to clean expired challenges:\", error);\n\t}\n\n\t// 2. Magic link / invite / signup tokens\n\ttry {\n\t\t// Cast needed: Database extends AuthTables but uses Generated<> wrappers\n\t\t// that confuse structural checks. The adapter casts internally anyway.\n\t\t// eslint-disable-next-line @typescript-eslint/no-unsafe-type-assertion -- Database uses Generated<> wrappers incompatible with AuthTables structurally; safe at runtime\n\t\tconst authAdapter = createKyselyAdapter(db as unknown as Kysely<AuthTables>);\n\t\tawait authAdapter.deleteExpiredTokens();\n\t\tresult.expiredTokens = 0; // deleteExpiredTokens returns void\n\t} catch (error) {\n\t\tconsole.error(\"[cleanup] Failed to clean expired tokens:\", error);\n\t}\n\n\t// 3. Pending media uploads (abandoned after 1 hour)\n\t//    Delete DB rows first, then remove corresponding files from storage.\n\ttry {\n\t\tconst mediaRepo = new MediaRepository(db);\n\t\tconst orphanedKeys = await mediaRepo.cleanupPendingUploads();\n\t\tresult.pendingUploads = orphanedKeys.length;\n\n\t\t// Delete orphaned files from object storage\n\t\tif (storage && orphanedKeys.length > 0) {\n\t\t\tlet filesDeleted = 0;\n\t\t\tfor (const key of orphanedKeys) {\n\t\t\t\ttry {\n\t\t\t\t\tawait storage.delete(key);\n\t\t\t\t\tfilesDeleted++;\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Log per-file failures but continue -- storage.delete is\n\t\t\t\t\t// documented as idempotent, so this is an unexpected error.\n\t\t\t\t\tconsole.error(`[cleanup] Failed to delete storage file ${key}:`, error);\n\t\t\t\t}\n\t\t\t}\n\t\t\tresult.pendingUploadFiles = filesDeleted;\n\t\t} else {\n\t\t\tresult.pendingUploadFiles = 0;\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(\"[cleanup] Failed to clean pending uploads:\", error);\n\t}\n\n\t// 4. Uploaded objects that lost publication races or outlived their media row\n\ttry {\n\t\tconst mediaRepo = new MediaRepository(db);\n\t\tconst completedAttemptsDeleted = await mediaRepo.deleteCompletedUploadAttempts();\n\t\tif (!storage) {\n\t\t\tresult.uploadAttempts = completedAttemptsDeleted;\n\t\t} else {\n\t\t\tconst storageKeys = await mediaRepo.findUploadAttemptsForCleanup();\n\t\t\tlet attemptsDeleted = completedAttemptsDeleted;\n\t\t\tfor (const storageKey of storageKeys) {\n\t\t\t\tif (await removeUploadAttempt(storage, mediaRepo, storageKey)) {\n\t\t\t\t\tattemptsDeleted++;\n\t\t\t\t}\n\t\t\t}\n\t\t\tresult.uploadAttempts = attemptsDeleted;\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(\"[cleanup] Failed to clean media upload attempts:\", error);\n\t}\n\n\ttry {\n\t\tresult.revisionsPruned = await pruneQueuedRevisions(db);\n\t} catch (error) {\n\t\tconsole.error(\"[cleanup] Failed to prune revisions:\", error);\n\t}\n\n\ttry {\n\t\tconst mediaUsage = await cleanupMediaUsage(db);\n\t\tresult.mediaUsage = mediaUsage.status === \"failed\" ? -1 : mediaUsage.deletedRows;\n\t} catch (error) {\n\t\tconsole.error(\"[cleanup] Failed to clean media usage:\", error);\n\t}\n\n\treturn result;\n}\n\nasync function pruneQueuedRevisions(db: Kysely<Database>): Promise<number> {\n\tconst queued = await db\n\t\t.selectFrom(\"_emdash_revision_prune_queue\")\n\t\t.selectAll()\n\t\t.orderBy(\"revision_id\")\n\t\t.limit(REVISION_PRUNE_BATCH_SIZE)\n\t\t.execute();\n\tconst revisionRepo = new RevisionRepository(db);\n\tlet totalPruned = 0;\n\n\tfor (const row of queued) {\n\t\ttry {\n\t\t\ttotalPruned += await revisionRepo.pruneQueuedEntry(\n\t\t\t\trow.collection,\n\t\t\t\trow.entry_id,\n\t\t\t\trow.revision_id,\n\t\t\t\tREVISION_KEEP_COUNT,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconsole.error(\n\t\t\t\t`[cleanup] Failed to prune revisions for ${row.collection}/${row.entry_id}:`,\n\t\t\t\terror,\n\t\t\t);\n\t\t}\n\t}\n\n\treturn totalPruned;\n}\n","/**\n * Built-in Default Comment Moderator\n *\n * Registers comment:moderate as an exclusive hook.\n * Implements the 4-step decision logic:\n *   1. Auto-approve authenticated CMS users (if configured)\n *   2. If moderation is \"none\" → approved\n *   3. If moderation is \"first_time\" and returning commenter → approved\n *   4. Otherwise → pending\n *\n * This moderator does not read `metadata` — it only uses collection settings\n * and prior approval count. Plugin moderators (AI, Akismet) replace this.\n */\n\nimport type { CommentModerateEvent, ModerationDecision, PluginContext } from \"../plugins/types.js\";\n\n/** Plugin ID for the built-in default comment moderator */\nexport const DEFAULT_COMMENT_MODERATOR_PLUGIN_ID = \"emdash-default-comment-moderator\";\n\n/**\n * The comment:moderate handler for the built-in default moderator.\n */\nexport async function defaultCommentModerate(\n\tevent: CommentModerateEvent,\n\t_ctx: PluginContext,\n): Promise<ModerationDecision> {\n\tconst { comment, collectionSettings, priorApprovedCount } = event;\n\n\t// 1. Auto-approve authenticated CMS users if configured\n\tif (collectionSettings.commentsAutoApproveUsers && comment.authorUserId) {\n\t\treturn { status: \"approved\", reason: \"Authenticated CMS user\" };\n\t}\n\n\t// 2. If moderation is \"none\" → approved\n\tif (collectionSettings.commentsModeration === \"none\") {\n\t\treturn { status: \"approved\", reason: \"Moderation disabled\" };\n\t}\n\n\t// 3. If moderation is \"first_time\" and returning commenter → approved\n\tif (collectionSettings.commentsModeration === \"first_time\" && priorApprovedCount > 0) {\n\t\treturn { status: \"approved\", reason: \"Returning commenter\" };\n\t}\n\n\t// 4. Otherwise → pending\n\treturn { status: \"pending\", reason: \"Held for review\" };\n}\n","/**\n * Scheduled publishing sweep\n *\n * Promotes content whose scheduled publish time has passed. Driven by the\n * platform scheduler alongside cron ticks and system cleanup — never by a\n * request. On Node the cron scheduler's maintenance pass calls it; on\n * Cloudflare the Worker's `scheduled()` handler does.\n *\n * Like `runSystemCleanup`, each collection sweep is independent and non-fatal:\n * one collection failing must not stop the rest.\n */\n\nimport type { Kysely } from \"kysely\";\n\nimport { handleContentPublish } from \"./api/handlers/content.js\";\nimport { ContentRepository } from \"./database/repositories/content.js\";\nimport type { Database } from \"./database/types.js\";\nimport { SchemaRegistry } from \"./schema/registry.js\";\n\n/** A content item that was promoted to published by a sweep. */\nexport interface PublishedRef {\n\tcollection: string;\n\tid: string;\n}\n\n/**\n * Default cap on items promoted per collection in a single sweep. Bounds the\n * publish/webhook fan-out of one tick so a large backlog can't exhaust a Worker\n * invocation's CPU/subrequest budget; the remainder drains on later ticks.\n */\nexport const SCHEDULED_PUBLISH_BATCH_LIMIT = 100;\n\n/**\n * Publishes a single content item. Mirrors the relevant subset of\n * `handleContentPublish`'s return shape. Production callers pass\n * `EmDashRuntime.handleContentPublish` so `content:afterPublish` hooks fire\n * (search indexing, webhooks, syndication); the default falls back to the raw\n * handler (no hooks) for callers that have only a `db`.\n */\nexport type ScheduledPublishFn = (\n\tcollection: string,\n\tid: string,\n\toptions: {\n\t\tpublishedAt?: string;\n\t\trequireScheduledDue?: boolean;\n\t\texpectedScheduledAt?: string;\n\t},\n) => Promise<{ success: boolean; error?: { code?: string } }>;\n\nexport interface PublishDueContentOptions {\n\t/**\n\t * Publish callback. Production callers pass the runtime's\n\t * `handleContentPublish` so `content:afterPublish` hooks fire (search\n\t * indexing, webhooks, syndication). Defaults to the raw DB handler (no hooks).\n\t */\n\tpublish?: ScheduledPublishFn;\n\t/**\n\t * Invoked after each collection's batch with the items promoted in that\n\t * batch. Lets request-less callers (the Cloudflare `scheduled()` handler)\n\t * purge edge-cache tags incrementally instead of only after the whole sweep,\n\t * so a runtime killed mid-sweep strands at most one batch behind stale cache\n\t * rather than everything published so far. Failures are logged, never fatal.\n\t */\n\tonPublished?: (refs: PublishedRef[]) => Promise<void>;\n\t/**\n\t * Maximum items promoted per collection per sweep. Defaults to\n\t * `SCHEDULED_PUBLISH_BATCH_LIMIT`. Pass `0` (or a negative) for unbounded.\n\t */\n\tlimit?: number;\n}\n\n/**\n * Publish every content item whose `scheduled_at` is in the past.\n *\n * Iterates all collections, finds due items (`findReadyToPublish` returns both\n * scheduled drafts and published entries with pending scheduled changes), and\n * publishes each. `publish()` clears `scheduled_at`, so a second sweep is a\n * no-op — safe to run on every tick.\n *\n * Bounded per collection by `limit` (default `SCHEDULED_PUBLISH_BATCH_LIMIT`):\n * a large backlog drains across successive ticks rather than in one unbounded\n * pass. After each collection's batch, `onPublished` (if given) is awaited so\n * cache-tag invalidation happens incrementally, not just at the very end.\n *\n * Returns every item it promoted so request-less callers (the Cloudflare\n * `scheduled()` handler) can also act on the full set.\n */\nexport async function publishDueContent(\n\tdb: Kysely<Database>,\n\toptions: PublishDueContentOptions = {},\n): Promise<PublishedRef[]> {\n\tconst { publish, onPublished, limit = SCHEDULED_PUBLISH_BATCH_LIMIT } = options;\n\tconst published: PublishedRef[] = [];\n\n\tlet collections;\n\ttry {\n\t\tcollections = await new SchemaRegistry(db).listCollections();\n\t} catch (error) {\n\t\tconsole.error(\"[scheduled-publish] Failed to list collections:\", error);\n\t\treturn published;\n\t}\n\n\tconst repo = new ContentRepository(db);\n\tconst doPublish: ScheduledPublishFn =\n\t\tpublish ?? ((collection, id, opts) => handleContentPublish(db, collection, id, opts));\n\t// 0 / negative means unbounded; findReadyToPublish treats that as \"no LIMIT\".\n\tconst batchLimit = limit > 0 ? limit : undefined;\n\n\tfor (const collection of collections) {\n\t\ttry {\n\t\t\tconst due = await repo.findReadyToPublish(collection.slug, batchLimit);\n\t\t\tconst batch: PublishedRef[] = [];\n\t\t\tfor (const item of due) {\n\t\t\t\t// First publication of a scheduled draft should record the intended\n\t\t\t\t// scheduled time, not the (later) sweep time. Items already published\n\t\t\t\t// with pending draft changes keep their original published_at.\n\t\t\t\tconst publishedAt = item.publishedAt == null ? (item.scheduledAt ?? undefined) : undefined;\n\t\t\t\tconst result = await doPublish(collection.slug, item.id, {\n\t\t\t\t\tpublishedAt,\n\t\t\t\t\trequireScheduledDue: true,\n\t\t\t\t\texpectedScheduledAt: item.scheduledAt ?? undefined,\n\t\t\t\t});\n\t\t\t\tif (result.success) {\n\t\t\t\t\tbatch.push({ collection: collection.slug, id: item.id });\n\t\t\t\t} else if (result.error?.code === \"NOT_DUE\") {\n\t\t\t\t\t// Unscheduled or rescheduled between selection and publish — the\n\t\t\t\t\t// editor changed their mind; skip quietly, not a failure.\n\t\t\t\t} else {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`[scheduled-publish] Failed to publish ${collection.slug}/${item.id}:`,\n\t\t\t\t\t\tresult.error,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (batch.length > 0) {\n\t\t\t\tpublished.push(...batch);\n\t\t\t\tif (onPublished) {\n\t\t\t\t\t// Purge this batch's cache tags before moving to the next\n\t\t\t\t\t// collection, so a mid-sweep kill can't strand already-published\n\t\t\t\t\t// content behind stale cache.\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait onPublished(batch);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t`[scheduled-publish] onPublished failed after \"${collection.slug}\" batch:`,\n\t\t\t\t\t\t\terror,\n\t\t\t\t\t\t);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(`[scheduled-publish] Sweep failed for \"${collection.slug}\":`, error);\n\t\t}\n\t}\n\n\treturn published;\n}\n","/**\n * EmDashRuntime - Core runtime for EmDash CMS\n *\n * Manages database, storage, plugins (trusted + sandboxed), hooks, and\n * provides handlers for content/media operations.\n *\n * Created once per worker lifetime, cached and reused across requests.\n */\n\nimport { ensureFrontendServiceAccount } from \"./auth/frontend-account.js\";\nimport { Permissions } from \"@premium-cms/auth\";\nimport type { Element } from \"@premium-cms/blocks\";\nimport { Kysely, sql, type Dialect } from \"kysely\";\nimport virtualConfig from \"virtual:emdash/config\";\nimport { z } from \"zod\";\n\nimport { GitContentStore, GitStoreError, gitConnection } from \"./content/git-store.js\";\nimport { assertMediaUsageActivationWriteAllowed } from \"./api/media-usage-write-fence.js\";\nimport { validateRev } from \"./api/rev.js\";\nimport type {\n\tEmDashConfig,\n\tPluginAdminPage,\n\tPluginDashboardWidget,\n} from \"./astro/integration/runtime.js\";\nimport type { EmDashManifest, ManifestCollection } from \"./astro/types.js\";\nimport { getAuthMode } from \"./auth/mode.js\";\nimport { getTrustedProxyHeaders } from \"./auth/trusted-proxy.js\";\nimport type { ContentFieldFilters } from \"./content-list-query.js\";\nimport { isSqlite } from \"./database/dialect-helpers.js\";\nimport { kyselyLogOption } from \"./database/instrumentation.js\";\nimport {\n\tenforceRuntimeMigrationPolicy,\n\tPendingMigrationsError,\n\ttype RuntimeMigrationMode,\n} from \"./database/migrations/policy.js\";\nimport {\n\tConcurrentMigrationTimeoutError,\n\tMIGRATION_RACE_WAIT_MS,\n} from \"./database/migrations/runner.js\";\nimport { AuditRepository } from \"./database/repositories/audit.js\";\nimport { ContentRepository } from \"./database/repositories/content.js\";\nimport { RevisionRepository } from \"./database/repositories/revision.js\";\nimport { ContentMutationConflictError } from \"./database/repositories/types.js\";\nimport type {\n\tContentItem as ContentItemInternal,\n\tContentDateField,\n} from \"./database/repositories/types.js\";\nimport { getI18nConfig } from \"./i18n/config.js\";\nimport { repairLocaleCasing } from \"./i18n/repair-locale-casing.js\";\nimport { warnAboutUnconfiguredTaxonomyLocales } from \"./i18n/taxonomy-locale-diagnostic.js\";\nimport { normalizeMediaValue } from \"./media/normalize.js\";\nimport type { MediaProvider, MediaProviderCapabilities } from \"./media/types.js\";\nimport {\n\tMEDIA_USAGE_COLLECTION_DELETION_LIMITS,\n\tprocessDueMediaUsageCollectionDeletions,\n} from \"./media/usage/collection-deletion-processor.js\";\nimport {\n\tdeleteContentMediaUsage,\n\tfindNonTranslatableSiblingContentIds,\n\tmarkContentMediaUsageCollectionStale,\n\trefreshContentMediaUsageAfterWrite,\n} from \"./media/usage/content-refresh.js\";\nimport {\n\tMEDIA_USAGE_RECONCILIATION_LIMITS,\n\tprocessDueMediaUsageReconciliation,\n} from \"./media/usage/reconciliation-processor.js\";\nimport {\n\tMEDIA_USAGE_WORK_PROCESSING_LIMITS,\n\tprocessDueMediaUsageWork,\n\tprocessMediaUsageWorkAfterWrite,\n} from \"./media/usage/work-processor.js\";\nimport { createSandboxRunnerOptions } from \"./plugins/sandbox/runner-options.js\";\nimport { getSandboxRouteErrorDetails } from \"./plugins/sandbox/types.js\";\nimport type {\n\tSandboxedPluginInstance,\n\tSandboxRunner,\n\tSandboxRunnerFactory,\n} from \"./plugins/sandbox/types.js\";\nimport type {\n\tResolvedPlugin,\n\tMediaItem,\n\tPluginManifest,\n\tPluginCapability,\n\tPluginStorageConfig,\n\tPluginMcpManifestConfig,\n\tPublicPageContext,\n\tPageMetadataContribution,\n\tPageFragmentContribution,\n\tPortableTextBlockConfig,\n\tFieldWidgetConfig,\n\tSettingField,\n\tUserInfo,\n} from \"./plugins/types.js\";\nimport { recordSchedulerHeartbeatSafely } from \"./scheduler-health.js\";\nimport { MAX_COLLECTION_LIST_COLUMNS, type FieldType } from \"./schema/types.js\";\nimport { isMissingTableError } from \"./utils/db-errors.js\";\nimport { hashString } from \"./utils/hash.js\";\nimport { createInitLock, type InitLock, initWithLock } from \"./utils/init-lock.js\";\nimport { createSingleFlightCache, singleFlightCached } from \"./utils/single-flight-cache.js\";\nimport { COMMIT, VERSION } from \"./version.js\";\n\nconst LEADING_SLASH_PATTERN = /^\\//;\nconst LOCALE_CASING_REPAIR_OPTION = \"emdash:repair_locale_casing\";\n\nfunction getLocaleCasingRepairVersion(locales: readonly string[]): string | null {\n\tif (locales.length === 0) return null;\n\treturn `1:${locales.toSorted().join(\",\")}`;\n}\n\n/**\n * Parse a JSON column expected to contain an array of strings.\n *\n * Throws on malformed JSON rather than returning []; callers are responsible\n * for deciding how to handle/log the error. Empty string / null inputs return\n * [] (they represent \"no value\"). Non-string array entries are filtered out.\n */\nfunction parseStringArray(raw: string | null | undefined): string[] {\n\tif (!raw) return [];\n\tconst parsed: unknown = JSON.parse(raw);\n\tif (!Array.isArray(parsed)) return [];\n\treturn parsed.filter((v): v is string => typeof v === \"string\");\n}\n\n/** Combined result from a single-pass page contribution collection */\ninterface PageContributions {\n\tmetadata: PageMetadataContribution[];\n\tfragments: PageFragmentContribution[];\n}\n\nconst VALID_METADATA_KINDS = new Set([\"meta\", \"property\", \"link\", \"jsonld\"]);\n\n/** Security-critical allowlist for link rel values from sandboxed plugins */\nconst VALID_LINK_REL = new Set([\n\t\"canonical\",\n\t\"alternate\",\n\t\"author\",\n\t\"license\",\n\t\"nlweb\",\n\t\"site.standard.document\",\n]);\n\n/**\n * Runtime validation for sandboxed plugin metadata contributions.\n * Sandboxed plugins return `unknown` across the RPC boundary — we must\n * verify the shape before passing to the metadata collector.\n */\nfunction isValidMetadataContribution(c: unknown): c is PageMetadataContribution {\n\tif (!c || typeof c !== \"object\" || !(\"kind\" in c)) return false;\n\tconst obj = c as Record<string, unknown>;\n\tif (typeof obj.kind !== \"string\" || !VALID_METADATA_KINDS.has(obj.kind)) return false;\n\n\tswitch (obj.kind) {\n\t\tcase \"meta\":\n\t\t\treturn typeof obj.name === \"string\" && typeof obj.content === \"string\";\n\t\tcase \"property\":\n\t\t\treturn typeof obj.property === \"string\" && typeof obj.content === \"string\";\n\t\tcase \"link\":\n\t\t\treturn (\n\t\t\t\ttypeof obj.href === \"string\" && typeof obj.rel === \"string\" && VALID_LINK_REL.has(obj.rel)\n\t\t\t);\n\t\tcase \"jsonld\":\n\t\t\treturn obj.graph != null && typeof obj.graph === \"object\";\n\t\tdefault:\n\t\t\treturn false;\n\t}\n}\n\nimport { after } from \"./after.js\";\nimport { maybeRunScheduledBackup } from \"./api/handlers/backup.js\";\nimport { loadBundleFromR2 } from \"./api/handlers/marketplace.js\";\nimport { runSystemCleanup } from \"./cleanup.js\";\nimport {\n\tDEFAULT_COMMENT_MODERATOR_PLUGIN_ID,\n\tdefaultCommentModerate,\n} from \"./comments/moderator.js\";\nimport { validateEncryptionKeyAtStartup } from \"./config/secrets.js\";\nimport { OptionsRepository } from \"./database/repositories/options.js\";\nimport {\n\thandleContentList,\n\thandleContentAuthors,\n\thandleContentGet,\n\thandleContentGetIncludingTrashed,\n\thandleContentCreate,\n\thandleContentUpdate,\n\thandleContentDelete,\n\thandleContentDuplicate,\n\thandleContentRestore,\n\thandleContentPermanentDelete,\n\thandleContentListTrashed,\n\thandleContentCountTrashed,\n\thandleContentPublish,\n\thandleContentUnpublish,\n\thandleContentSchedule,\n\thandleContentUnschedule,\n\thandleContentCountScheduled,\n\thandleContentDiscardDraft,\n\thandleContentCompare,\n\thandleContentTranslations,\n\thandleMediaList,\n\thandleMediaGet,\n\thandleMediaCreate,\n\thandleMediaUpdate,\n\thandleMediaDelete,\n\thandleRevisionList,\n\thandleRevisionGet,\n\thandleRevisionRestore,\n\tSchemaRegistry,\n\ttype Database,\n\ttype Storage,\n} from \"./index.js\";\nimport { getDb } from \"./loader.js\";\nimport { isRecord } from \"./plugin-utils.js\";\nimport { CronExecutor, type InvokeCronHookFn } from \"./plugins/cron.js\";\nimport { definePlugin } from \"./plugins/define-plugin.js\";\nimport { DEV_CONSOLE_EMAIL_PLUGIN_ID, devConsoleEmailDeliver } from \"./plugins/email-console.js\";\nimport { EmailPipeline } from \"./plugins/email.js\";\nimport {\n\tcreateHookPipeline,\n\tresolveExclusiveHooks as resolveExclusiveHooksShared,\n\ttype HookPipeline,\n} from \"./plugins/hooks.js\";\nimport { normalizeManifestRoute } from \"./plugins/manifest-schema.js\";\nimport { extractRequestMeta, sanitizeHeadersForSandbox } from \"./plugins/request-meta.js\";\nimport {\n\tbuildRouteMeta,\n\tparseRouteInput,\n\tPluginRouteRegistry,\n\ttoRouteCallerInfo,\n\ttype RouteCallerInput,\n\ttype RouteMeta,\n} from \"./plugins/routes.js\";\nimport type { CronScheduler } from \"./plugins/scheduler/types.js\";\nimport { PluginStateRepository } from \"./plugins/state.js\";\nimport { syncDeclaredStorageIndexes } from \"./plugins/storage-indexes.js\";\nimport { normalizeRegistryConfig } from \"./registry/config.js\";\nimport { requestCached } from \"./request-cache.js\";\nimport { getRequestContext } from \"./request-context.js\";\nimport { publishDueContent, type PublishedRef } from \"./scheduled-publish.js\";\nimport { FTSManager } from \"./search/fts-manager.js\";\nimport { invalidateSiteSettingsCache } from \"./settings/index.js\";\n\n/**\n * Map schema field types to editor field kinds\n */\nconst FIELD_TYPE_TO_KIND: Record<FieldType, string> = {\n\tstring: \"string\",\n\tslug: \"string\",\n\turl: \"url\",\n\ttext: \"richText\",\n\tnumber: \"number\",\n\tinteger: \"number\",\n\tboolean: \"boolean\",\n\tdatetime: \"datetime\",\n\tselect: \"select\",\n\tmultiSelect: \"multiSelect\",\n\tportableText: \"portableText\",\n\timage: \"image\",\n\tfile: \"file\",\n\treference: \"reference\",\n\tjson: \"json\",\n\trepeater: \"repeater\",\n};\n\nconst DRAFT_ONLY_UPDATE_KEYS = new Set([\"data\", \"slug\", \"locale\", \"skipRevision\"]);\nconst MAX_DRAFT_STAGE_ATTEMPTS = 32;\n\nconst LIST_COLUMN_FIELD_TYPES: ReadonlySet<FieldType> = new Set([\n\t\"string\",\n\t\"number\",\n\t\"integer\",\n\t\"boolean\",\n\t\"datetime\",\n\t\"select\",\n\t\"multiSelect\",\n]);\n\n/**\n * Sandboxed plugin entry from virtual module\n */\nexport interface SandboxedPluginEntry {\n\tid: string;\n\tversion: string;\n\toptions: Record<string, unknown>;\n\tcode: string;\n\t/** Capabilities the plugin requests */\n\tcapabilities: PluginCapability[];\n\t/** Allowed hosts for network:fetch */\n\tallowedHosts: string[];\n\t/** Declared storage collections */\n\tstorage: PluginStorageConfig;\n\t/** Serialized MCP declarations emitted at plugin build time. */\n\tmcp?: PluginMcpManifestConfig;\n\t/** Route declarations (name + public/permission/cacheControl), used for route auth decisions */\n\troutes?: PluginManifest[\"routes\"];\n\t/** Hook declarations this plugin implements */\n\thooks?: PluginManifest[\"hooks\"];\n\t/** Admin pages */\n\tadminPages?: Array<{ path: string; label?: string; icon?: string }>;\n\t/** Dashboard widgets */\n\tadminWidgets?: Array<{ id: string; title?: string; size?: string }>;\n\t/** Settings schema for the auto-generated admin settings form */\n\tsettingsSchema?: Record<string, SettingField>;\n\t/** Portable Text block types contributed to the editor (declarative Block Kit) */\n\tportableTextBlocks?: PortableTextBlockConfig[];\n\t/** Field widget types contributed for schema-field editing UIs */\n\tfieldWidgets?: FieldWidgetConfig[];\n\t/** Admin entry module */\n\tadminEntry?: string;\n\t/**\n\t * Exclusive hooks this plugin should be auto-selected for.\n\t * Weaker than an existing admin DB selection — config order wins when no selection exists.\n\t */\n\tpreferred?: string[];\n}\n\n/**\n * Media provider entry from virtual module\n */\nexport interface MediaProviderEntry {\n\tid: string;\n\tname: string;\n\ticon?: string;\n\tcapabilities: MediaProviderCapabilities;\n\t/** Factory function to create the provider instance */\n\tcreateProvider: (ctx: MediaProviderContext) => MediaProvider;\n}\n\n/**\n * Context passed to media provider factory functions\n */\nexport interface MediaProviderContext {\n\tdb: Kysely<Database>;\n\t/**\n\t * Resolver for the live connection, preferred over `db` by providers that\n\t * query EmDash's database. Resolves the current request/event-scoped\n\t * connection from ALS so connection-backed adapters (Postgres over\n\t * Hyperdrive) don't reuse the per-isolate singleton's socket across events.\n\t * Providers should resolve per operation rather than capturing `db` once.\n\t * Omitted-safe: falls back to `db` for stateless adapters (D1, Node SQLite).\n\t */\n\tgetDb?: () => Kysely<Database>;\n\tstorage: Storage | null;\n}\n\n/**\n * Builds the timer-based scheduler that drives cron ticks and maintenance.\n * Injected via `virtual:emdash/scheduler` so the platform — not core — decides\n * whether a long-lived heartbeat exists.\n */\nexport type CreateSchedulerFn = (executor: CronExecutor) => CronScheduler;\n\n/**\n * Dependencies injected from virtual modules (middleware reads these)\n */\nexport interface RuntimeDependencies {\n\tconfig: EmDashConfig;\n\t/** Effective migration mode, resolved once by the runtime entrypoint. */\n\tmigrationMode?: RuntimeMigrationMode;\n\tplugins: ResolvedPlugin[];\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tcreateDialect: (config: any) => Dialect;\n\t/**\n\t * Factory for a dialect that batches same-turn reads into one round trip\n\t * ({@link EmDashRuntime.create} uses it for the cold-start read phase).\n\t * Present only on batching backends (D1, DO); absent backends fall back to\n\t * the singleton. Returns a fresh connection each call — it must never be the\n\t * long-lived singleton, whose coalescing buffer would be shared across\n\t * requests.\n\t */\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tcreateCoalescingDialect?: (config: any) => Dialect | null;\n\t// eslint-disable-next-line @typescript-eslint/no-explicit-any\n\tcreateStorage: ((config: any) => Storage) | null;\n\tsandboxEnabled: boolean;\n\t/** sandbox: false escape hatch - load sandboxed plugins in-process */\n\tsandboxBypassed?: boolean;\n\t/**\n\t * Factory for the timer-based cron/maintenance heartbeat. Supplied by the\n\t * generated `virtual:emdash/scheduler` module: a `NodeCronScheduler` factory\n\t * on long-lived runtimes (Node/Bun), or `null` on serverless adapters where\n\t * an external driver (e.g. the Cloudflare Worker's `scheduled()` Cron\n\t * Trigger) calls `runScheduledTasks()` instead. When absent or null, the\n\t * runtime starts no scheduler. Keeping the platform decision in the\n\t * integration means core has no adapter-specific runtime checks.\n\t */\n\tcreateScheduler?: CreateSchedulerFn | null;\n\t/** Media provider entries from virtual module */\n\tmediaProviderEntries?: MediaProviderEntry[];\n\tsandboxedPluginEntries: SandboxedPluginEntry[];\n\t/** Factory function supplied by the active platform adapter. */\n\tcreateSandboxRunner: SandboxRunnerFactory | null;\n}\n\n/**\n * Constructor parameters for `EmDashRuntime`.\n *\n * Production code should use `EmDashRuntime.create()` which discovers and\n * loads all parts (database, plugins, hooks, cron, etc.) and then calls the\n * constructor. Direct construction is supported for callers that already\n * have all the dependencies in hand — for example, integration tests that\n * supply a pre-migrated database and an empty plugin set.\n *\n * Every field corresponds 1:1 to internal state set on the runtime — none of\n * these are derived. If you don't have a value for one, see what `create()`\n * passes for that field as the canonical default.\n */\nexport interface EmDashRuntimeParts {\n\tdb: Kysely<Database>;\n\tstorage: Storage | null;\n\tconfiguredPlugins: ResolvedPlugin[];\n\tsandboxedPlugins: Map<string, SandboxedPluginInstance>;\n\tsandboxedPluginEntries: SandboxedPluginEntry[];\n\thooks: HookPipeline;\n\tenabledPlugins: Set<string>;\n\tpluginStates: Map<string, string>;\n\tconfig: EmDashConfig;\n\tmediaProviders: Map<string, MediaProvider>;\n\tmediaProviderEntries: MediaProviderEntry[];\n\tcronExecutor: CronExecutor | null;\n\tcronScheduler: CronScheduler | null;\n\temailPipeline: EmailPipeline | null;\n\tallPipelinePlugins: ResolvedPlugin[];\n\tpipelineFactoryOptions: {\n\t\tdb: Kysely<Database>;\n\t\tgetDb?: () => Kysely<Database>;\n\t\tbeforeContentWrite?: () => Promise<void>;\n\t\tstorage?: Storage;\n\t\tsiteInfo?: {\n\t\t\tsiteName?: string;\n\t\t\tsiteUrl?: string;\n\t\t\tplatformUrl?: string;\n\t\t\tlocale?: string;\n\t\t\ttrailingSlash?: \"always\" | \"never\" | \"ignore\";\n\t\t};\n\t};\n\truntimeDeps: RuntimeDependencies;\n\tpipelineRef: { current: HookPipeline };\n}\n\n/**\n * Convert a ContentItem to Record<string, unknown> for hook consumption.\n * Hooks receive the full item as a flat record.\n */\nfunction contentItemToRecord(item: ContentItemInternal): Record<string, unknown> {\n\treturn { ...item };\n}\n\n/**\n * Db init lock reclaim deadline. Derived from the migration race wait so\n * they can't drift apart: a healthy init can legitimately block for the\n * full MIGRATION_RACE_WAIT_MS inside waitForConcurrentMigrator, plus cold\n * connect and migrator work, before it should be presumed dead. The outer\n * runtime init lock (middleware.ts) must use a strictly larger deadline —\n * it wraps create() → getDatabase() → this lock, and equal deadlines would\n * let the outer reclaim while the inner is legitimately still working.\n */\nexport const DB_INIT_DEADLINE_MS = MIGRATION_RACE_WAIT_MS + 20_000;\n\n/**\n * Db cache + its init lock live on globalThis behind a Symbol: the bundler\n * can duplicate this module across SSR chunks (same reasoning as\n * request-cache.ts), and a duplicated cache/lock would mean concurrent\n * independent db inits — and duplicate migrators — per isolate.\n */\nconst DB_HOLDER_KEY = Symbol.for(\"emdash:db-cache\");\ninterface DbHolder {\n\tcache: Map<string, Kysely<Database>>;\n\tlock: InitLock;\n\t/**\n\t * Recent migration failures, keyed like `cache`. A failed migration is\n\t * near-certain to fail again immediately (schema conflict, broken\n\t * migration), and without this every request in a warm isolate would\n\t * re-attempt it — on Workers with Postgres that stampedes the database\n\t * through the migration advisory lock (#1744). Entries expire after\n\t * DB_INIT_FAILURE_BACKOFF_MS; a successful init clears them.\n\t */\n\tfailures: Map<string, { at: number; message: string }>;\n}\nconst globalSymbolStore = globalThis as Record<symbol, unknown>;\nfunction getDbHolder(): DbHolder {\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis symbol slot, written only below\n\tlet holder = globalSymbolStore[DB_HOLDER_KEY] as DbHolder | undefined;\n\tif (!holder) {\n\t\tholder = {\n\t\t\tcache: new Map<string, Kysely<Database>>(),\n\t\t\tlock: createInitLock(),\n\t\t\tfailures: new Map(),\n\t\t};\n\t\tglobalSymbolStore[DB_HOLDER_KEY] = holder;\n\t}\n\t// A holder created by an older copy of this module (dev-server HMR keeps\n\t// globalThis across reloads) may predate the failures map.\n\tholder.failures ??= new Map();\n\treturn holder;\n}\n\n/**\n * After a database init fails (migrations threw), skip re-attempting for\n * this long. Cold isolates still get one attempt each, so a transient\n * failure heals on its own; a persistently failing migration is retried at\n * most once per backoff window per isolate instead of on every request.\n */\nconst DB_INIT_FAILURE_BACKOFF_MS = 30_000;\n\n/**\n * Auto-seed runs at most once per isolate per database. Its lock + \"done\" set\n * live on globalThis (same bundler-duplication reasoning as the db cache) so a\n * reclaimed-and-rerun `create()` can't seed a second time concurrently. The\n * lock polls rather than sharing a promise, so it is safe to await across a\n * cancelled owner in workerd.\n */\nconst SEED_HOLDER_KEY = Symbol.for(\"emdash:seed-state\");\ninterface SeedHolder {\n\tdone: Set<string>;\n\tlock: InitLock;\n}\nfunction getSeedHolder(): SeedHolder {\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis symbol slot, written only below\n\tlet holder = globalSymbolStore[SEED_HOLDER_KEY] as SeedHolder | undefined;\n\tif (!holder) {\n\t\tholder = { done: new Set<string>(), lock: createInitLock() };\n\t\tglobalSymbolStore[SEED_HOLDER_KEY] = holder;\n\t}\n\treturn holder;\n}\nconst storageCache = new Map<string, Storage>();\nconst sandboxedPluginCache = new Map<string, SandboxedPluginInstance>();\n/**\n * Per-tier sets of `${pluginId}:${version}` keys present in\n * `sandboxedPluginCache`. Used during sync to know which entries belong\n * to which install source so we can invalidate only what belongs to the\n * tier currently being synced.\n */\nconst marketplacePluginKeys = new Set<string>();\nconst registryPluginKeys = new Set<string>();\n/**\n * Manifest metadata for runtime-installed sandboxed plugins (marketplace\n * and registry both). Keyed by `pluginId`; readers don't care which\n * source the plugin came from. Named `marketplace*` for legacy reasons.\n */\nconst marketplaceManifestCache = new Map<\n\tstring,\n\t{\n\t\tid: string;\n\t\tversion: string;\n\t\tadmin?: {\n\t\t\tpages?: PluginAdminPage[];\n\t\t\twidgets?: PluginDashboardWidget[];\n\t\t\tsettingsSchema?: Record<string, SettingField>;\n\t\t};\n\t\tmcp?: PluginMcpManifestConfig;\n\t\tstorage?: PluginManifest[\"storage\"];\n\t}\n>();\n/** Route metadata for sandboxed plugins: pluginId -> routeName -> RouteMeta */\nconst sandboxedRouteMetaCache = new Map<string, Map<string, RouteMeta>>();\nlet sandboxRunner: SandboxRunner | null = null;\n\nexport const MEDIA_USAGE_MAINTENANCE_QUERY_RESERVATIONS = Object.freeze({\n\tentryWork: MEDIA_USAGE_WORK_PROCESSING_LIMITS.ordinaryStatementsPerJob,\n\tcollectionDeletion: MEDIA_USAGE_COLLECTION_DELETION_LIMITS.maxQueriesPerTick,\n\treconciliation: MEDIA_USAGE_RECONCILIATION_LIMITS.maxQueriesPerTick,\n\tmaxClassQueries: Math.max(\n\t\tMEDIA_USAGE_WORK_PROCESSING_LIMITS.ordinaryStatementsPerJob,\n\t\tMEDIA_USAGE_COLLECTION_DELETION_LIMITS.maxQueriesPerTick,\n\t\tMEDIA_USAGE_RECONCILIATION_LIMITS.maxQueriesPerTick,\n\t),\n\teventCeiling: 40,\n});\n\nexport type MediaUsageMaintenanceTaskClass =\n\t| \"entry_work\"\n\t| \"collection_deletion\"\n\t| \"reconciliation\";\n\nexport type MediaUsageMaintenanceResult =\n\t| { outcome: \"inactive\" | \"admission_closed\"; taskClass: null; turn: null }\n\t| { outcome: \"processed\"; taskClass: MediaUsageMaintenanceTaskClass; turn: number };\n\nasync function runScheduledMediaUsageLane(\n\tdb: Kysely<Database>,\n): Promise<MediaUsageMaintenanceResult> {\n\tconst queriesAlreadySpent = getRequestContext()?.metrics?.dbCount ?? 0;\n\tif (\n\t\tqueriesAlreadySpent + 1 + MEDIA_USAGE_MAINTENANCE_QUERY_RESERVATIONS.maxClassQueries >\n\t\tMEDIA_USAGE_MAINTENANCE_QUERY_RESERVATIONS.eventCeiling\n\t) {\n\t\treturn { outcome: \"admission_closed\", taskClass: null, turn: null };\n\t}\n\n\tconst activation = await db\n\t\t.updateTable(\"_emdash_media_usage_activation\")\n\t\t.set({\n\t\t\tmedia_usage_maintenance_turn: sql<number>`(media_usage_maintenance_turn + 1) % 3`,\n\t\t})\n\t\t.where(\"task_key\", \"=\", \"incremental_capture\")\n\t\t.where(\"state\", \"=\", \"active\")\n\t\t.returning(\"media_usage_maintenance_turn\")\n\t\t.executeTakeFirst();\n\tif (!activation) return { outcome: \"inactive\", taskClass: null, turn: null };\n\n\tconst turn = activation.media_usage_maintenance_turn;\n\tif (turn === 0) {\n\t\tawait processDueMediaUsageWork(db);\n\t\treturn { outcome: \"processed\", taskClass: \"entry_work\", turn };\n\t}\n\tif (turn === 1) {\n\t\tawait processDueMediaUsageCollectionDeletions(db);\n\t\treturn { outcome: \"processed\", taskClass: \"collection_deletion\", turn };\n\t}\n\tawait processDueMediaUsageReconciliation(db);\n\treturn { outcome: \"processed\", taskClass: \"reconciliation\", turn };\n}\n\n/**\n * EmDashRuntime - singleton per worker\n */\nexport class EmDashRuntime {\n\t/**\n\t * The singleton database instance (worker-lifetime cached).\n\t * Use the `db` getter instead — it checks the request context first\n\t * for per-request overrides (D1 read replica sessions, DO multi-site).\n\t */\n\tprivate readonly _db: Kysely<Database>;\n\treadonly storage: Storage | null;\n\treadonly configuredPlugins: ResolvedPlugin[];\n\treadonly sandboxedPlugins: Map<string, SandboxedPluginInstance>;\n\treadonly sandboxedPluginEntries: SandboxedPluginEntry[];\n\t/**\n\t * Schema registry bound to the current request/event-scoped connection.\n\t * Built per access (SchemaRegistry just wraps a db) against `this.db`, the\n\t * ALS-aware getter — never a captured snapshot of the singleton. On a\n\t * connection-backed adapter (Postgres over Hyperdrive) a captured singleton\n\t * would query a socket opened by an earlier event and trip workerd's\n\t * cross-request I/O guard; the catch in handlers like handleContentUpdate\n\t * would then silently treat a revision-enabled collection as non-revisioned\n\t * and write draft edits to live columns. Same reasoning as the per-call\n\t * registry in _buildManifest().\n\t */\n\tget schemaRegistry(): SchemaRegistry {\n\t\treturn new SchemaRegistry(this.db);\n\t}\n\tprivate _hooks!: HookPipeline;\n\treadonly config: EmDashConfig;\n\treadonly mediaProviders: Map<string, MediaProvider>;\n\treadonly mediaProviderEntries: MediaProviderEntry[];\n\treadonly cronExecutor: CronExecutor | null;\n\treadonly email: EmailPipeline | null;\n\n\tprivate cronScheduler: CronScheduler | null;\n\tprivate enabledPlugins: Set<string>;\n\tprivate pluginStates: Map<string, string>;\n\n\t/**\n\t * Isolate-lifetime guard so FTS indexes are verified at most once per\n\t * worker rather than on every admin request. See ensureSearchHealthy().\n\t * Uses the poison-immune single-flight cache (never a shared awaitable\n\t * promise) so a cancelled first caller can't wedge later ones.\n\t */\n\tprivate readonly _searchHealthCache = createSingleFlightCache<void>();\n\n\t/** Current hook pipeline. Use the `hooks` getter for external access. */\n\tget hooks(): HookPipeline {\n\t\treturn this._hooks;\n\t}\n\n\t/** All plugins eligible for the hook pipeline (includes built-in plugins).\n\t *  Stored so we can rebuild the pipeline when plugins are enabled/disabled. */\n\tprivate allPipelinePlugins: ResolvedPlugin[];\n\t/** Guards the once-per-process plugin storage-index sync. */\n\tprivate storageIndexesSynced = false;\n\t/** Factory options for the hook pipeline context factory */\n\tprivate pipelineFactoryOptions: {\n\t\tdb: Kysely<Database>;\n\t\tgetDb?: () => Kysely<Database>;\n\t\tbeforeContentWrite?: () => Promise<void>;\n\t\tstorage?: Storage;\n\t\tsiteInfo?: {\n\t\t\tsiteName?: string;\n\t\t\tsiteUrl?: string;\n\t\t\tplatformUrl?: string;\n\t\t\tlocale?: string;\n\t\t\ttrailingSlash?: \"always\" | \"never\" | \"ignore\";\n\t\t};\n\t};\n\t/** Dependencies needed for exclusive hook resolution */\n\tprivate runtimeDeps: RuntimeDependencies;\n\t/** Mutable ref for the cron invokeCronHook closure to read the current pipeline */\n\tprivate pipelineRef!: { current: HookPipeline };\n\n\t/**\n\t * Get the database instance for the current request.\n\t *\n\t * Checks the ALS-based request context first — middleware sets a\n\t * per-request Kysely instance there for D1 read replica sessions\n\t * or DO preview databases. Falls back to the singleton instance.\n\t */\n\tget db(): Kysely<Database> {\n\t\tconst ctx = getRequestContext();\n\t\tif (ctx?.db) {\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- db in context is set by middleware with correct type\n\t\t\treturn ctx.db as Kysely<Database>;\n\t\t}\n\t\treturn this._db;\n\t}\n\n\tconstructor(parts: EmDashRuntimeParts) {\n\t\tthis._db = parts.db;\n\t\tthis.storage = parts.storage;\n\t\tthis.configuredPlugins = parts.configuredPlugins;\n\t\tthis.sandboxedPlugins = parts.sandboxedPlugins;\n\t\tthis.sandboxedPluginEntries = parts.sandboxedPluginEntries;\n\t\tthis._hooks = parts.hooks;\n\t\tthis.enabledPlugins = parts.enabledPlugins;\n\t\tthis.pluginStates = parts.pluginStates;\n\t\tthis.config = parts.config;\n\t\tthis.mediaProviders = parts.mediaProviders;\n\t\tthis.mediaProviderEntries = parts.mediaProviderEntries;\n\t\tthis.cronExecutor = parts.cronExecutor;\n\t\tthis.cronScheduler = parts.cronScheduler;\n\t\tthis.email = parts.emailPipeline;\n\t\tthis.allPipelinePlugins = parts.allPipelinePlugins;\n\t\tthis.pipelineFactoryOptions = parts.pipelineFactoryOptions;\n\t\tthis.runtimeDeps = parts.runtimeDeps;\n\t\tthis.pipelineRef = parts.pipelineRef;\n\t}\n\n\t/**\n\t * Get the sandbox runner instance (for marketplace install/update)\n\t */\n\tgetSandboxRunner(): SandboxRunner | null {\n\t\treturn sandboxRunner;\n\t}\n\n\t/**\n\t * Whether the sandbox bypass mode (sandbox: false) is active.\n\t * Marketplace install/update handlers use this to skip the\n\t * SANDBOX_NOT_AVAILABLE gate, since the bypass path loads\n\t * marketplace plugins in-process via syncMarketplacePlugins().\n\t */\n\tisSandboxBypassed(): boolean {\n\t\treturn this.runtimeDeps.sandboxBypassed === true;\n\t}\n\n\t/**\n\t * Publish any content whose scheduled time has passed.\n\t * Returns the items promoted so callers can invalidate their cache tags.\n\t */\n\tasync publishScheduled(): Promise<PublishedRef[]> {\n\t\treturn this.publishScheduledWithFence();\n\t}\n\n\tprivate async publishScheduledWithFence(\n\t\tonPublished?: (refs: PublishedRef[]) => Promise<void>,\n\t): Promise<PublishedRef[]> {\n\t\tawait assertMediaUsageActivationWriteAllowed(this.db);\n\t\treturn publishDueContent(this.db, {\n\t\t\tpublish: (collection, id, options) => this.handleContentPublish(collection, id, options),\n\t\t\tonPublished,\n\t\t});\n\t}\n\n\t/**\n\t * Run the full scheduled-maintenance batch: cron tasks, scheduled\n\t * publishing, and system cleanup. For request-less drivers — the\n\t * Cloudflare `scheduled()` handler invokes this from a Cron Trigger.\n\t * (On Node the timer-based scheduler drives the same work itself.)\n\t *\n\t * Each step is independent and non-fatal. Returns the content promoted\n\t * by the publishing sweep so the caller can purge edge-cache tags.\n\t *\n\t * `onPublished` (optional) is awaited after each collection's batch so a\n\t * request-less driver can invalidate edge-cache tags incrementally rather\n\t * than only after the whole sweep — bounding stale-cache exposure if the\n\t * runtime is killed mid-sweep.\n\t */\n\tasync runScheduledTasks(\n\t\toptions: {\n\t\t\tonPublished?: (refs: PublishedRef[]) => Promise<void>;\n\t\t} = {},\n\t): Promise<{ published: PublishedRef[] }> {\n\t\tif (this.cronExecutor) {\n\t\t\ttry {\n\t\t\t\tawait this.cronExecutor.tick();\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"[cron] Tick failed:\", error);\n\t\t\t}\n\t\t\ttry {\n\t\t\t\tawait this.cronExecutor.recoverStaleLocks();\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\"[cron] Stale lock recovery failed:\", error);\n\t\t\t}\n\t\t}\n\n\t\tlet published: PublishedRef[] = [];\n\t\ttry {\n\t\t\tpublished = await this.publishScheduledWithFence(options.onPublished);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[scheduled-publish] Sweep failed:\", error);\n\t\t}\n\n\t\ttry {\n\t\t\tawait runSystemCleanup(this.db, this.storage ?? undefined);\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[cleanup] System cleanup failed:\", error);\n\t\t}\n\n\t\ttry {\n\t\t\tawait this.syncPluginStorageIndexesOnce();\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[plugins] Storage index sync failed:\", error);\n\t\t}\n\n\t\t// Never throws; no-op unless scheduled backups are enabled and due.\n\t\tawait maybeRunScheduledBackup(this.db, this.storage ?? undefined);\n\t\tawait recordSchedulerHeartbeatSafely(this.db);\n\n\t\treturn { published };\n\t}\n\n\tasync runScheduledMediaUsageTasks(): Promise<MediaUsageMaintenanceResult> {\n\t\treturn runScheduledMediaUsageLane(this.db);\n\t}\n\n\t/**\n\t * Materialize plugin-declared storage indexes, once per process.\n\t *\n\t * Called from the scheduler path, not from request handlers — configured\n\t * plugins have no install handler, so the tick is their only sync moment.\n\t */\n\tasync syncPluginStorageIndexesOnce(): Promise<void> {\n\t\tif (this.storageIndexesSynced) return;\n\t\tthis.storageIndexesSynced = true;\n\t\t// Sandboxed marketplace/registry plugins never join allPipelinePlugins;\n\t\t// their manifests are cached at bundle load. Without them, plugins\n\t\t// installed before this feature shipped would never get their indexes.\n\t\tawait syncDeclaredStorageIndexes(this.db, [\n\t\t\t...this.allPipelinePlugins,\n\t\t\t...marketplaceManifestCache.values(),\n\t\t]);\n\t}\n\n\t/**\n\t * Stop the cron scheduler gracefully.\n\t * Call during worker shutdown or hot-reload.\n\t */\n\tasync stopCron(): Promise<void> {\n\t\tif (this.cronScheduler) {\n\t\t\tawait this.cronScheduler.stop();\n\t\t}\n\t}\n\n\t/**\n\t * Update in-memory plugin status and rebuild the hook pipeline.\n\t *\n\t * Rebuilding the pipeline ensures disabled plugins' hooks stop firing\n\t * and re-enabled plugins' hooks start firing again without a restart.\n\t * Exclusive hook selections are re-resolved after each rebuild.\n\t */\n\tasync setPluginStatus(pluginId: string, status: \"active\" | \"inactive\"): Promise<void> {\n\t\tthis.pluginStates.set(pluginId, status);\n\t\tif (status === \"active\") {\n\t\t\tthis.enabledPlugins.add(pluginId);\n\t\t\tawait this.rebuildHookPipeline();\n\t\t\tawait this._hooks.runPluginActivate(pluginId);\n\t\t} else {\n\t\t\t// Fire deactivate on the current pipeline while the plugin is still in it\n\t\t\tawait this._hooks.runPluginDeactivate(pluginId);\n\t\t\tthis.enabledPlugins.delete(pluginId);\n\t\t\tawait this.rebuildHookPipeline();\n\t\t}\n\t}\n\n\t/**\n\t * Rebuild the hook pipeline from the current set of enabled plugins.\n\t *\n\t * Filters `allPipelinePlugins` to only those in `enabledPlugins`,\n\t * creates a fresh HookPipeline, re-resolves exclusive hook selections,\n\t * and re-wires the context factory so existing references (cron\n\t * callbacks, email pipeline) use the new pipeline.\n\t */\n\tprivate async rebuildHookPipeline(): Promise<void> {\n\t\tconst enabledList = this.allPipelinePlugins.filter((p) => this.enabledPlugins.has(p.id));\n\t\tconst newPipeline = createHookPipeline(enabledList, this.pipelineFactoryOptions);\n\n\t\t// Re-resolve exclusive hooks against the new pipeline\n\t\tawait EmDashRuntime.resolveExclusiveHooks(newPipeline, this.db, this.runtimeDeps);\n\n\t\t// Carry over context factory options from the old pipeline so that\n\t\t// email, cron reschedule, and other wired-in options are preserved.\n\t\t// The old pipeline's contextFactoryOptions were built up incrementally\n\t\t// via setContextFactory calls during create(). We replay them here.\n\t\tif (this.email) {\n\t\t\t// db/getDb are already wired by createHookPipeline above (they live in\n\t\t\t// pipelineFactoryOptions), so the merge only adds emailPipeline.\n\t\t\tnewPipeline.setContextFactory({ emailPipeline: this.email });\n\t\t}\n\t\tnewPipeline.setContextFactory({\n\t\t\t// Plugin schedules remain database-backed when no in-process scheduler\n\t\t\t// exists; an external trigger is responsible for invoking due tasks.\n\t\t\tcronReschedule: () => this.cronScheduler?.reschedule(),\n\t\t});\n\n\t\t// Update the email pipeline to use the new hook pipeline\n\t\tif (this.email) {\n\t\t\tthis.email.setPipeline(newPipeline);\n\t\t}\n\n\t\t// Update the mutable ref so the cron closure dispatches through\n\t\t// the new pipeline without needing to reconstruct the CronExecutor.\n\t\tthis.pipelineRef.current = newPipeline;\n\n\t\tthis._hooks = newPipeline;\n\t}\n\n\t/**\n\t * Synchronize marketplace plugin runtime state with DB + storage.\n\t *\n\t * Ensures install/update/uninstall changes take effect immediately in the\n\t * current worker: loads newly active plugins and removes uninstalled ones.\n\t */\n\t/** When this isolate last reconciled its loaded plugins with `plugin_state` (see `resyncPluginsIfStale`). */\n\tprivate pluginSyncAt = Date.now();\n\n\t/**\n\t * Plugins change in ONE isolate: the install / update / enable / uninstall\n\t * route calls `syncMarketplacePlugins()` there, and every other isolate of the\n\t * worker (other colos, other instances) keeps serving the build it loaded at\n\t * start until it is recycled — which can take hours on a quiet site. Request\n\t * handling calls this instead: at most once per `maxAgeMs` per isolate it\n\t * re-reads `plugin_state` (one small query) and loads what changed, so a\n\t * plugin update reaches every isolate within the window.\n\t */\n\tasync resyncPluginsIfStale(maxAgeMs = 30_000): Promise<void> {\n\t\tconst now = Date.now();\n\t\tif (now - this.pluginSyncAt < maxAgeMs) return;\n\t\tthis.pluginSyncAt = now;\n\t\ttry {\n\t\t\tawait this.syncMarketplacePlugins();\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[emdash] periodic plugin sync failed:\", error);\n\t\t}\n\t}\n\n\tasync syncMarketplacePlugins(): Promise<void> {\n\t\tthis.pluginSyncAt = Date.now();\n\t\tif (!this.config.marketplace) return;\n\n\t\t// In sandbox bypass mode (sandbox: false), the noop runner reports\n\t\t// unavailable but we still want admin metadata for newly installed\n\t\t// marketplace plugins to refresh in-process. Hooks/routes still won't\n\t\t// execute (matches the cold-start bypass behavior), but Configure\n\t\t// links and admin pages appear immediately.\n\t\tif (this.runtimeDeps.sandboxBypassed) {\n\t\t\tawait this.syncMarketplacePluginsBypassed();\n\t\t\treturn;\n\t\t}\n\n\t\tawait this.syncSandboxedSourcePlugins(\"marketplace\");\n\t}\n\n\t/**\n\t * Synchronize registry plugin runtime state with DB + storage.\n\t *\n\t * Mirrors {@link syncMarketplacePlugins} for plugins installed via the\n\t * experimental decentralized plugin registry. Called after install,\n\t * update, and uninstall handlers complete.\n\t */\n\tasync syncRegistryPlugins(): Promise<void> {\n\t\tif (!this.config.experimental?.registry) return;\n\t\tawait this.syncSandboxedSourcePlugins(\"registry\");\n\t}\n\n\t/**\n\t * Internal: reconcile in-memory sandboxed-plugin state with the\n\t * `_plugin_state` table for the given source tier. Shared\n\t * implementation behind {@link syncMarketplacePlugins} and\n\t * {@link syncRegistryPlugins}.\n\t *\n\t * Each source tier has its own key set in `${source}PluginKeys` so a\n\t * sync for one tier doesn't invalidate the other.\n\t */\n\tprivate async syncSandboxedSourcePlugins(source: \"marketplace\" | \"registry\"): Promise<void> {\n\t\tif (!this.storage) return;\n\t\tif (!sandboxRunner || !sandboxRunner.isAvailable()) return;\n\n\t\tconst keySet = source === \"marketplace\" ? marketplacePluginKeys : registryPluginKeys;\n\n\t\ttry {\n\t\t\tconst stateRepo = new PluginStateRepository(this.db);\n\t\t\tconst states =\n\t\t\t\tsource === \"marketplace\"\n\t\t\t\t\t? await stateRepo.getMarketplacePlugins()\n\t\t\t\t\t: await stateRepo.getRegistryPlugins();\n\n\t\t\tconst desired = new Map<string, string>();\n\t\t\tfor (const state of states) {\n\t\t\t\tthis.pluginStates.set(state.pluginId, state.status);\n\t\t\t\tif (state.status === \"active\") {\n\t\t\t\t\tthis.enabledPlugins.add(state.pluginId);\n\t\t\t\t} else {\n\t\t\t\t\tthis.enabledPlugins.delete(state.pluginId);\n\t\t\t\t}\n\t\t\t\tif (state.status !== \"active\") continue;\n\t\t\t\t// Marketplace plugins use `marketplaceVersion` when present;\n\t\t\t\t// registry plugins always use `version`.\n\t\t\t\tconst desiredVersion =\n\t\t\t\t\tsource === \"marketplace\" ? (state.marketplaceVersion ?? state.version) : state.version;\n\t\t\t\tdesired.set(state.pluginId, desiredVersion);\n\t\t\t}\n\n\t\t\t// Remove uninstalled or no-longer-active plugins from memory.\n\t\t\tconst keysToRemove: string[] = [];\n\t\t\tfor (const key of keySet) {\n\t\t\t\tconst [pluginId] = key.split(\":\");\n\t\t\t\tif (!pluginId) continue;\n\t\t\t\tconst desiredVersion = desired.get(pluginId);\n\t\t\t\tif (desiredVersion && key === `${pluginId}:${desiredVersion}`) continue;\n\t\t\t\tkeysToRemove.push(key);\n\t\t\t}\n\n\t\t\tfor (const key of keysToRemove) {\n\t\t\t\tconst [pluginId] = key.split(\":\");\n\t\t\t\tif (!pluginId) continue;\n\t\t\t\tconst desiredVersion = desired.get(pluginId);\n\t\t\t\tif (!desiredVersion) {\n\t\t\t\t\tthis.pluginStates.delete(pluginId);\n\t\t\t\t\tthis.enabledPlugins.delete(pluginId);\n\t\t\t\t}\n\n\t\t\t\tconst existing = sandboxedPluginCache.get(key);\n\t\t\t\tif (existing) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait existing.terminate();\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tconsole.warn(`EmDash: Failed to terminate sandboxed plugin ${key}:`, error);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tsandboxedPluginCache.delete(key);\n\t\t\t\tthis.sandboxedPlugins.delete(key);\n\t\t\t\tkeySet.delete(key);\n\t\t\t\tif (pluginId) {\n\t\t\t\t\tsandboxedRouteMetaCache.delete(pluginId);\n\t\t\t\t\tmarketplaceManifestCache.delete(pluginId);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Load newly active plugins.\n\t\t\tfor (const [pluginId, version] of desired) {\n\t\t\t\tconst key = `${pluginId}:${version}`;\n\t\t\t\tif (sandboxedPluginCache.has(key)) {\n\t\t\t\t\tkeySet.add(key);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst bundle = await loadBundleFromR2(this.storage, pluginId, version, source);\n\t\t\t\tif (!bundle) {\n\t\t\t\t\tconsole.warn(`EmDash: ${source} plugin ${pluginId}@${version} not found in R2`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tconst loaded = await sandboxRunner.load(bundle.manifest, bundle.backendCode);\n\t\t\t\tsandboxedPluginCache.set(key, loaded);\n\t\t\t\tthis.sandboxedPlugins.set(key, loaded);\n\t\t\t\tkeySet.add(key);\n\n\t\t\t\t// Cache manifest admin config for getManifest()\n\t\t\t\tmarketplaceManifestCache.set(pluginId, {\n\t\t\t\t\tid: bundle.manifest.id,\n\t\t\t\t\tversion: bundle.manifest.version,\n\t\t\t\t\tadmin: bundle.manifest.admin,\n\t\t\t\t\tmcp: bundle.manifest.mcp,\n\t\t\t\t\tstorage: bundle.manifest.storage,\n\t\t\t\t});\n\n\t\t\t\t// Cache route metadata from manifest for auth decisions\n\t\t\t\tif (bundle.manifest.routes.length > 0) {\n\t\t\t\t\tconst routeMetaMap = new Map<string, RouteMeta>();\n\t\t\t\t\tfor (const entry of bundle.manifest.routes) {\n\t\t\t\t\t\tconst normalized = normalizeManifestRoute(entry);\n\t\t\t\t\t\trouteMetaMap.set(normalized.name, buildRouteMeta(normalized));\n\t\t\t\t\t}\n\t\t\t\t\tsandboxedRouteMetaCache.set(pluginId, routeMetaMap);\n\t\t\t\t} else {\n\t\t\t\t\tsandboxedRouteMetaCache.delete(pluginId);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(`EmDash: Failed to sync ${source} plugins:`, error);\n\t\t}\n\t}\n\n\t/**\n\t * Remove a plugin from the in-memory pipeline lists by ID.\n\t * Mutates allPipelinePlugins and configuredPlugins in place.\n\t */\n\tprivate removePluginFromLists(pluginId: string): void {\n\t\tconst allIdx = this.allPipelinePlugins.findIndex((p) => p.id === pluginId);\n\t\tif (allIdx !== -1) this.allPipelinePlugins.splice(allIdx, 1);\n\t\tconst configIdx = this.configuredPlugins.findIndex((p) => p.id === pluginId);\n\t\tif (configIdx !== -1) this.configuredPlugins.splice(configIdx, 1);\n\t}\n\n\t/**\n\t * Sync marketplace plugin metadata in sandbox: false bypass mode.\n\t *\n\t * In bypass mode the noop runner can't load plugins, but admin pages,\n\t * widgets, and route metadata still need to refresh in-process when an\n\t * admin installs/updates/uninstalls a marketplace plugin. Otherwise the\n\t * admin UI shows stale data until the server restarts.\n\t *\n\t * Hooks and routes still won't execute under bypass (matches the\n\t * cold-start bypass behavior in loadMarketplacePluginsBypassed).\n\t *\n\t * Known limitation: bypass plugins are loaded via `import(dataUrl)`,\n\t * which Node's ESM cache keys on the full URL. Updates create fresh\n\t * module objects, but old ones remain cached for the worker's lifetime.\n\t * In practice this is a few KB per update — only matters for sites with\n\t * very frequent marketplace updates running long-lived processes. The\n\t * fix would be vm.SourceTextModule for explicit lifecycle management.\n\t */\n\tprivate async syncMarketplacePluginsBypassed(): Promise<void> {\n\t\tif (!this.storage) return;\n\t\ttry {\n\t\t\tconst stateRepo = new PluginStateRepository(this.db);\n\t\t\tconst marketplaceStates = await stateRepo.getMarketplacePlugins();\n\n\t\t\tconst desired = new Map<string, string>();\n\t\t\tfor (const state of marketplaceStates) {\n\t\t\t\tthis.pluginStates.set(state.pluginId, state.status);\n\t\t\t\tif (state.status === \"active\") {\n\t\t\t\t\tthis.enabledPlugins.add(state.pluginId);\n\t\t\t\t} else {\n\t\t\t\t\tthis.enabledPlugins.delete(state.pluginId);\n\t\t\t\t}\n\t\t\t\tif (state.status !== \"active\") continue;\n\t\t\t\tdesired.set(state.pluginId, state.marketplaceVersion ?? state.version);\n\t\t\t}\n\n\t\t\t// Drop metadata for plugins no longer active.\n\t\t\tconst toRemove: string[] = [];\n\t\t\tfor (const pluginId of marketplaceManifestCache.keys()) {\n\t\t\t\tif (!desired.has(pluginId)) toRemove.push(pluginId);\n\t\t\t}\n\t\t\tfor (const pluginId of toRemove) {\n\t\t\t\t// Fire plugin:deactivate hook before removal\n\t\t\t\tconst resolved = this.allPipelinePlugins.find((p) => p.id === pluginId);\n\t\t\t\tif (resolved) {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst deactivateHook = resolved.hooks?.[\"plugin:deactivate\"];\n\t\t\t\t\t\tif (deactivateHook) {\n\t\t\t\t\t\t\tconst handler =\n\t\t\t\t\t\t\t\ttypeof deactivateHook === \"function\" ? deactivateHook : deactivateHook.handler;\n\t\t\t\t\t\t\tif (typeof handler === \"function\") {\n\t\t\t\t\t\t\t\t// Sandbox-bypass cleanup: the plugin context isn't constructable\n\t\t\t\t\t\t\t\t// here (no DB binding, no media, etc.), but well-behaved\n\t\t\t\t\t\t\t\t// deactivate hooks should be no-op safe. If a hook does require\n\t\t\t\t\t\t\t\t// ctx, it throws and the surrounding catch logs it.\n\t\t\t\t\t\t\t\t// eslint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- best-effort cleanup; see comment above\n\t\t\t\t\t\t\t\tawait handler({ pluginId }, {} as never);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\tconsole.warn(`[emdash] plugin:deactivate hook failed for ${pluginId}:`, err);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tmarketplaceManifestCache.delete(pluginId);\n\t\t\t\tsandboxedRouteMetaCache.delete(pluginId);\n\t\t\t\t// Remove from pipeline lists too (mutate in place since the\n\t\t\t\t// arrays are readonly references but mutable contents)\n\t\t\t\tthis.removePluginFromLists(pluginId);\n\t\t\t\tthis.enabledPlugins.delete(pluginId);\n\t\t\t}\n\n\t\t\t// Load plugin code, adapt as trusted plugins, and add to pipeline lists\n\t\t\tconst { adaptSandboxEntry } = await import(\"./plugins/adapt-sandbox-entry.js\");\n\t\t\tconst newPlugins: ResolvedPlugin[] = [];\n\t\t\tfor (const [pluginId, version] of desired) {\n\t\t\t\tconst bundle = await loadBundleFromR2(this.storage, pluginId, version);\n\t\t\t\tif (!bundle) {\n\t\t\t\t\tconsole.warn(`EmDash: Marketplace plugin ${pluginId}@${version} not found in R2`);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tmarketplaceManifestCache.set(pluginId, {\n\t\t\t\t\tid: bundle.manifest.id,\n\t\t\t\t\tversion: bundle.manifest.version,\n\t\t\t\t\tadmin: bundle.manifest.admin,\n\t\t\t\t\tmcp: bundle.manifest.mcp,\n\t\t\t\t\tstorage: bundle.manifest.storage,\n\t\t\t\t});\n\t\t\t\tif (bundle.manifest.routes.length > 0) {\n\t\t\t\t\tconst routeMetaMap = new Map<string, RouteMeta>();\n\t\t\t\t\tfor (const entry of bundle.manifest.routes) {\n\t\t\t\t\t\tconst normalized = normalizeManifestRoute(entry);\n\t\t\t\t\t\trouteMetaMap.set(normalized.name, buildRouteMeta(normalized));\n\t\t\t\t\t}\n\t\t\t\t\tsandboxedRouteMetaCache.set(pluginId, routeMetaMap);\n\t\t\t\t} else {\n\t\t\t\t\tsandboxedRouteMetaCache.delete(pluginId);\n\t\t\t\t}\n\n\t\t\t\t// Skip if already in the pipeline at this version\n\t\t\t\tconst existing = this.allPipelinePlugins.find((p) => p.id === pluginId);\n\t\t\t\tif (existing && existing.version === bundle.manifest.version) continue;\n\n\t\t\t\t// Remove any older version\n\t\t\t\tif (existing) {\n\t\t\t\t\tthis.removePluginFromLists(pluginId);\n\t\t\t\t}\n\n\t\t\t\ttry {\n\t\t\t\t\tconst dataUrl = `data:text/javascript;base64,${Buffer.from(bundle.backendCode).toString(\"base64\")}`;\n\t\t\t\t\t// Dynamic data: import returns `any` from a base64-encoded module.\n\t\t\t\t\t// We trust the bundle to be shaped like a plugin (built by plugin-cli);\n\t\t\t\t\t// adaptSandboxEntry then validates fields it cares about.\n\t\t\t\t\t// eslint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- dynamic module from trusted bundle\n\t\t\t\t\tconst pluginModule = (await import(/* @vite-ignore */ dataUrl)) as Record<\n\t\t\t\t\t\tstring,\n\t\t\t\t\t\tunknown\n\t\t\t\t\t>;\n\t\t\t\t\tconst pluginDef = (pluginModule.default ?? pluginModule) as Parameters<\n\t\t\t\t\t\ttypeof adaptSandboxEntry\n\t\t\t\t\t>[0];\n\t\t\t\t\tconst adapted = adaptSandboxEntry(pluginDef, {\n\t\t\t\t\t\tid: bundle.manifest.id,\n\t\t\t\t\t\tversion: bundle.manifest.version,\n\t\t\t\t\t\tentrypoint: \"\",\n\t\t\t\t\t\tcapabilities: bundle.manifest.capabilities ?? [],\n\t\t\t\t\t\tallowedHosts: bundle.manifest.allowedHosts ?? [],\n\t\t\t\t\t\t// eslint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- adaptSandboxEntry copies storage through\n\t\t\t\t\t\tstorage: (bundle.manifest.storage ?? {}) as never,\n\t\t\t\t\t\tadminPages: bundle.manifest.admin?.pages,\n\t\t\t\t\t\tadminWidgets: bundle.manifest.admin?.widgets?.map((w) => ({\n\t\t\t\t\t\t\tid: w.id,\n\t\t\t\t\t\t\ttitle: w.title,\n\t\t\t\t\t\t\tsize:\n\t\t\t\t\t\t\t\tw.size === \"full\" || w.size === \"half\" || w.size === \"third\" ? w.size : undefined,\n\t\t\t\t\t\t})),\n\t\t\t\t\t\tsettingsSchema: bundle.manifest.admin?.settingsSchema,\n\t\t\t\t\t});\n\t\t\t\t\tnewPlugins.push(adapted);\n\t\t\t\t\tthis.allPipelinePlugins.push(adapted);\n\t\t\t\t\tthis.configuredPlugins.push(adapted);\n\t\t\t\t\tthis.enabledPlugins.add(adapted.id);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`EmDash: Failed to load marketplace plugin ${pluginId}@${version} in-process:`,\n\t\t\t\t\t\terror,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// If anything changed, rebuild the hook pipeline so new/removed\n\t\t\t// plugins take effect immediately without a server restart.\n\t\t\tif (toRemove.length > 0 || newPlugins.length > 0) {\n\t\t\t\tawait this.rebuildHookPipeline();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(\"EmDash: Failed to sync marketplace plugins (bypass):\", error);\n\t\t}\n\t}\n\n\t/**\n\t * Create and initialize the runtime\n\t */\n\tstatic async create(\n\t\tdeps: RuntimeDependencies,\n\t\ttimings?: Array<{ name: string; dur: number; desc?: string }>,\n\t): Promise<EmDashRuntime> {\n\t\t// Helper: time a phase and push into the shared timings array when\n\t\t// provided. Uses performance.now() — monotonic across async boundaries.\n\t\t// No-op when `timings` wasn't passed (preserves backwards compatibility\n\t\t// with callers that don't care about per-phase breakdown).\n\t\tconst phase = async <T>(name: string, desc: string, fn: () => Promise<T>): Promise<T> => {\n\t\t\tif (!timings) return fn();\n\t\t\tconst t0 = performance.now();\n\t\t\ttry {\n\t\t\t\treturn await fn();\n\t\t\t} finally {\n\t\t\t\ttimings.push({ name, dur: performance.now() - t0, desc });\n\t\t\t}\n\t\t};\n\n\t\t// Initialize the database and enforce its configured migration policy.\n\t\tconst db = await phase(\"rt.db\", \"DB init + migration policy\", () =>\n\t\t\tEmDashRuntime.getDatabase(deps),\n\t\t);\n\n\t\t// Resolver for the live connection, mirroring the `get db()` getter\n\t\t// below (which can't be used here — the runtime instance doesn't exist\n\t\t// yet). Long-lived subsystems built during create() (cron executor,\n\t\t// plugin context factory, media providers) capture this resolver rather\n\t\t// than the `db` snapshot, so a connection-backed adapter (Postgres over\n\t\t// Hyperdrive) serves their queries from the current request/event-scoped\n\t\t// connection in ALS instead of the per-isolate singleton — whose socket\n\t\t// belongs to an earlier request and would trip workerd's cross-request\n\t\t// I/O guard. Stateless adapters (D1, Node SQLite) set no ALS db on most\n\t\t// paths, so this falls back to the singleton: unchanged behavior.\n\t\tconst resolveDb = (): Kysely<Database> => {\n\t\t\tconst ctx = getRequestContext();\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- ALS db is typed unknown to avoid a circular import; middleware always sets a Kysely<Database>\n\t\t\treturn (ctx?.db as Kysely<Database> | undefined) ?? db;\n\t\t};\n\n\t\t// Validate EMDASH_ENCRYPTION_KEY once here so a malformed value\n\t\t// surfaces in startup logs instead of as request-time 500s. The key\n\t\t// itself is not yet consumed (a follow-up PR adds plugin-secret\n\t\t// encryption); validating early just guards against silent\n\t\t// misconfiguration.\n\t\tawait phase(\"rt.secrets\", \"Validate encryption key\", () => validateEncryptionKeyAtStartup());\n\n\t\t// FTS verify/repair is deferred off the cold-start hot path.\n\t\t// See EmDashRuntime.ensureSearchHealthy().\n\n\t\t// Initialize storage (sync)\n\t\tconst storage = EmDashRuntime.getStorage(deps);\n\n\t\tlet pluginStates: Map<string, string> = new Map();\n\t\tconst configuredLocales: string[] =\n\t\t\tvirtualConfig?.i18n?.locales ?? getI18nConfig()?.locales ?? [];\n\t\tconst localeCasingRepairVersion = getLocaleCasingRepairVersion(configuredLocales);\n\t\tlet storedLocaleCasingRepairVersion: string | undefined;\n\t\tlet siteInfo:\n\t\t\t| {\n\t\t\t\t\tsiteName?: string;\n\t\t\t\t\tsiteUrl?: string;\n\t\t\t\t\tlocale?: string;\n\t\t\t\t\ttrailingSlash?: \"always\" | \"never\" | \"ignore\";\n\t\t\t  }\n\t\t\t| undefined;\n\t\t// \"Already set up\" by default so a read failure (e.g. tables absent on a\n\t\t// pre-migration db) skips seeding rather than seeding a half-built db.\n\t\tlet seedGate = { collectionCount: 1, setupDone: true };\n\n\t\t// Seeding must only touch the configured singleton, never a borrowed\n\t\t// per-request db (playground / DO preview) or the loader-fallback db.\n\t\tconst reqCtx = getRequestContext();\n\t\tconst ownsConfiguredDb = !!deps.config.database && !(reqCtx?.dbIsIsolated && reqCtx.db);\n\n\t\t// Run the init reads on a coalescing connection so the concurrent\n\t\t// same-turn reads flush as one batch() round trip. The singleton can't:\n\t\t// its plain SqliteAdapter reports supportsMultipleConnections=false, so\n\t\t// Kysely's connection mutex serializes concurrent reads into N round\n\t\t// trips. The connection is per-init and discarded — the long-lived\n\t\t// singleton must never coalesce or one request's reads could land in\n\t\t// another's batch. Backends without a coalescing dialect (Node/SQLite)\n\t\t// fall back to the singleton, where serialization costs nothing.\n\t\tlet readDb = db;\n\t\tlet readDbDisposable: Kysely<Database> | undefined;\n\t\tconst disposeReadDb = async () => {\n\t\t\tconst disposable = readDbDisposable;\n\t\t\treadDbDisposable = undefined;\n\t\t\tif (!disposable) return;\n\t\t\ttry {\n\t\t\t\tawait disposable.destroy();\n\t\t\t} catch {\n\t\t\t\t// Non-fatal — the underlying binding is shared and needs no teardown.\n\t\t\t}\n\t\t};\n\t\tif (ownsConfiguredDb && deps.createCoalescingDialect && deps.config.database) {\n\t\t\ttry {\n\t\t\t\tconst dialect = deps.createCoalescingDialect(deps.config.database.config);\n\t\t\t\tif (dialect) {\n\t\t\t\t\treadDb = new Kysely<Database>({ dialect, log: kyselyLogOption() });\n\t\t\t\t\treadDbDisposable = readDb;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\treadDb = db;\n\t\t\t}\n\t\t}\n\t\tconst optionsRepo = new OptionsRepository(readDb);\n\t\tlet missingManualSchemaError: unknown;\n\t\tconst captureMissingManualSchema = (error: unknown) => {\n\t\t\tif ((deps.migrationMode ?? \"auto\") === \"manual\" && isMissingTableError(error)) {\n\t\t\t\tmissingManualSchemaError ??= error;\n\t\t\t}\n\t\t};\n\n\t\tconst readSiteInfo = async () => {\n\t\t\tconst siteOpts = await optionsRepo.getMany<string>([\n\t\t\t\t\"emdash:site_title\",\n\t\t\t\t\"emdash:site_url\",\n\t\t\t\t\"emdash:locale\",\n\t\t\t\t\"custom_domain:default_url\",\n\t\t\t\tLOCALE_CASING_REPAIR_OPTION,\n\t\t\t]);\n\t\t\tstoredLocaleCasingRepairVersion = siteOpts.get(LOCALE_CASING_REPAIR_OPTION);\n\t\t\treturn {\n\t\t\t\tsiteName: siteOpts.get(\"emdash:site_title\") ?? undefined,\n\t\t\t\tsiteUrl: siteOpts.get(\"emdash:site_url\") ?? undefined,\n\t\t\t\t// The platform origin a control plane gave this site; plugins use it\n\t\t\t\t// for platform-hosted URLs (previews) that must not follow a custom domain.\n\t\t\t\tplatformUrl: siteOpts.get(\"custom_domain:default_url\") ?? undefined,\n\t\t\t\tlocale: siteOpts.get(\"emdash:locale\") ?? undefined,\n\t\t\t\t// trailingSlash is a build-time Astro routing decision, not a\n\t\t\t\t// user-editable setting, so it comes from the Astro config\n\t\t\t\t// (virtual:emdash/config), not the options table.\n\t\t\t\ttrailingSlash: virtualConfig?.trailingSlash,\n\t\t\t};\n\t\t};\n\n\t\tconst coldStartReads: Array<Promise<void>> = [\n\t\t\tphase(\"rt.plugins\", \"Plugin states\", async () => {\n\t\t\t\ttry {\n\t\t\t\t\tconst states = await readDb\n\t\t\t\t\t\t.selectFrom(\"_plugin_state\")\n\t\t\t\t\t\t.select([\"plugin_id\", \"status\"])\n\t\t\t\t\t\t.execute();\n\t\t\t\t\tpluginStates = new Map(states.map((s) => [s.plugin_id, s.status]));\n\t\t\t\t} catch (error) {\n\t\t\t\t\tcaptureMissingManualSchema(error);\n\t\t\t\t\t// _plugin_state may not exist yet on a pre-migration db.\n\t\t\t\t}\n\t\t\t}),\n\t\t\tphase(\"rt.site\", \"Site info options\", async () => {\n\t\t\t\ttry {\n\t\t\t\t\tsiteInfo = await readSiteInfo();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tcaptureMissingManualSchema(error);\n\t\t\t\t\t// options may not exist yet on a pre-migration db.\n\t\t\t\t}\n\t\t\t}),\n\t\t];\n\n\t\tif (ownsConfiguredDb) {\n\t\t\tcoldStartReads.push(\n\t\t\t\tphase(\"rt.seedcheck\", \"Auto-seed gate\", async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst [collectionCount, setupOption] = await Promise.all([\n\t\t\t\t\t\t\treadDb\n\t\t\t\t\t\t\t\t.selectFrom(\"_emdash_collections\")\n\t\t\t\t\t\t\t\t.select((eb) => eb.fn.countAll<number>().as(\"count\"))\n\t\t\t\t\t\t\t\t.executeTakeFirstOrThrow(),\n\t\t\t\t\t\t\treadDb\n\t\t\t\t\t\t\t\t.selectFrom(\"options\")\n\t\t\t\t\t\t\t\t.select(\"value\")\n\t\t\t\t\t\t\t\t.where(\"name\", \"=\", \"emdash:setup_complete\")\n\t\t\t\t\t\t\t\t.executeTakeFirst(),\n\t\t\t\t\t\t]);\n\t\t\t\t\t\tconst setupDone = (() => {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\treturn !!setupOption && JSON.parse(setupOption.value) === true;\n\t\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t\treturn false;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t})();\n\t\t\t\t\t\tseedGate = { collectionCount: collectionCount.count, setupDone };\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tcaptureMissingManualSchema(error);\n\t\t\t\t\t\t// Leave the \"already set up\" default so a read failure never\n\t\t\t\t\t\t// triggers a seed onto a half-built db.\n\t\t\t\t\t}\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\tawait Promise.all(coldStartReads);\n\n\t\tif (ownsConfiguredDb) {\n\t\t\t// The public frontend's service account + API token (Settings → frontend token).\n\t\t\tawait phase(\"rt.frontend\", \"Frontend service account\", async () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait ensureFrontendServiceAccount(db);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tcaptureMissingManualSchema(error);\n\t\t\t\t\t// authz tables may not exist yet on a pre-migration db; retried next boot.\n\t\t\t\t}\n\t\t\t});\n\t\t}\n\t\tif (missingManualSchemaError) {\n\t\t\tawait disposeReadDb();\n\t\t\tthrow missingManualSchemaError;\n\t\t}\n\n\t\tif (\n\t\t\tlocaleCasingRepairVersion &&\n\t\t\t(configuredLocales.some(\n\t\t\t\t(locale) => locale.includes(\"-\") || locale !== locale.toLowerCase(),\n\t\t\t) ||\n\t\t\t\tstoredLocaleCasingRepairVersion !== undefined) &&\n\t\t\tstoredLocaleCasingRepairVersion !== localeCasingRepairVersion\n\t\t) {\n\t\t\tawait phase(\"rt.locale\", \"Repair locale casing\", async () => {\n\t\t\t\tawait repairLocaleCasing(db, configuredLocales);\n\t\t\t\tawait new OptionsRepository(db).set(LOCALE_CASING_REPAIR_OPTION, localeCasingRepairVersion);\n\t\t\t});\n\t\t}\n\n\t\t// Auto-seed the default schema for a first load that skipped the setup\n\t\t// wizard (the wizard and dev-bypass apply seeds explicitly). Run under a\n\t\t// per-isolate lock keyed by the configured db so a reclaimed-and-rerun\n\t\t// create() can't apply the seed a second time concurrently.\n\t\tif (seedGate.collectionCount === 0 && !seedGate.setupDone) {\n\t\t\tconst seedKey = deps.config.database?.entrypoint ?? \"default\";\n\t\t\tconst seedHolder = getSeedHolder();\n\t\t\ttry {\n\t\t\t\tawait initWithLock(\n\t\t\t\t\tseedHolder.lock,\n\t\t\t\t\t() => (seedHolder.done.has(seedKey) ? true : undefined),\n\t\t\t\t\tasync () => {\n\t\t\t\t\t\tconst { applySeed } = await import(\"./seed/apply.js\");\n\t\t\t\t\t\tconst { loadSeed } = await import(\"./seed/load.js\");\n\t\t\t\t\t\tconst { validateSeed } = await import(\"./seed/validate.js\");\n\n\t\t\t\t\t\tconst seed = await loadSeed();\n\t\t\t\t\t\tconst validation = validateSeed(seed);\n\t\t\t\t\t\tif (validation.valid) {\n\t\t\t\t\t\t\tawait applySeed(db, seed, { onConflict: \"skip\" });\n\t\t\t\t\t\t\tconsole.log(\"Auto-seeded default collections\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\tseedHolder.done.add(seedKey);\n\t\t\t\t\t\treturn true;\n\t\t\t\t\t},\n\t\t\t\t\t{ deadlineMs: DB_INIT_DEADLINE_MS, anchor: (promise) => after(() => promise) },\n\t\t\t\t);\n\t\t\t\t// The site-info read ran before the seed wrote its defaults, so\n\t\t\t\t// refresh the snapshot the plugin context sees.\n\t\t\t\ttry {\n\t\t\t\t\tsiteInfo = await readSiteInfo();\n\t\t\t\t} catch {\n\t\t\t\t\t// Non-fatal — plugin context falls back to undefined fields.\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Non-fatal — a failed seed (e.g. missing seed module) leaves the\n\t\t\t\t// site un-seeded; a later request retries.\n\t\t\t}\n\t\t}\n\n\t\t// The read connection is single-use; everything below uses the singleton.\n\t\tawait disposeReadDb();\n\n\t\tconst enabledPlugins = new Set<string>();\n\t\tfor (const plugin of deps.plugins) {\n\t\t\tconst status = pluginStates.get(plugin.id);\n\t\t\tif (status === undefined || status === \"active\") {\n\t\t\t\tenabledPlugins.add(plugin.id);\n\t\t\t}\n\t\t}\n\n\t\t// Build the full list of pipeline-eligible plugins: all configured\n\t\t// plugins (regardless of current enabled status) plus built-in plugins.\n\t\t// rebuildHookPipeline() filters this to only enabled plugins.\n\t\tconst allPipelinePlugins: ResolvedPlugin[] = [...deps.plugins];\n\n\t\t// Collected bypassed plugins (sandbox: false escape hatch).\n\t\t// These need to be added to BOTH the pipeline (for hooks) AND the\n\t\t// configuredPlugins list (for route dispatch).\n\t\tconst bypassedPluginsList: ResolvedPlugin[] = [];\n\n\t\t// In dev mode, register a built-in console email provider.\n\t\t// It participates in exclusive hook resolution like any other plugin —\n\t\t// auto-selected when it's the sole provider, overridden when a real one is configured.\n\t\t// Gated by import.meta.env.DEV to prevent silent email loss in production.\n\t\tif (import.meta.env.DEV) {\n\t\t\ttry {\n\t\t\t\tconst devConsolePlugin = definePlugin({\n\t\t\t\t\tid: DEV_CONSOLE_EMAIL_PLUGIN_ID,\n\t\t\t\t\tversion: \"0.0.0\",\n\t\t\t\t\tcapabilities: [\"hooks.email-transport:register\"],\n\t\t\t\t\thooks: {\n\t\t\t\t\t\t\"email:deliver\": {\n\t\t\t\t\t\t\texclusive: true,\n\t\t\t\t\t\t\thandler: devConsoleEmailDeliver,\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t});\n\t\t\t\tallPipelinePlugins.push(devConsolePlugin);\n\t\t\t\t// Built-in plugins are always enabled\n\t\t\t\tenabledPlugins.add(devConsolePlugin.id);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn(\"[email] Failed to register dev console email provider:\", error);\n\t\t\t}\n\t\t}\n\n\t\t// Register built-in default comment moderator.\n\t\t// Always present — auto-selected as the sole comment:moderate provider\n\t\t// unless a plugin (e.g. AI moderation) provides its own.\n\t\ttry {\n\t\t\tconst defaultModeratorPlugin = definePlugin({\n\t\t\t\tid: DEFAULT_COMMENT_MODERATOR_PLUGIN_ID,\n\t\t\t\tversion: \"0.0.0\",\n\t\t\t\tcapabilities: [\"users:read\"],\n\t\t\t\thooks: {\n\t\t\t\t\t\"comment:moderate\": {\n\t\t\t\t\t\texclusive: true,\n\t\t\t\t\t\thandler: defaultCommentModerate,\n\t\t\t\t\t},\n\t\t\t\t},\n\t\t\t});\n\t\t\tallPipelinePlugins.push(defaultModeratorPlugin);\n\t\t\t// Built-in plugins are always enabled\n\t\t\tenabledPlugins.add(defaultModeratorPlugin.id);\n\t\t} catch (error) {\n\t\t\tconsole.warn(\"[comments] Failed to register default moderator:\", error);\n\t\t}\n\n\t\t// sandbox: false escape hatch - load sandboxed plugin entries in-process\n\t\t// as trusted plugins (no isolation) so they participate in the hook pipeline.\n\t\t// Block this on Cloudflare Workers where dynamic import(dataUrl) is not\n\t\t// available and running untrusted code in-process is a security risk.\n\t\tif (deps.sandboxBypassed && deps.sandboxedPluginEntries.length > 0) {\n\t\t\tconst isCfWorkers =\n\t\t\t\ttypeof navigator !== \"undefined\" &&\n\t\t\t\ttypeof navigator.userAgent === \"string\" &&\n\t\t\t\tnavigator.userAgent.includes(\"Cloudflare-Workers\");\n\t\t\tif (isCfWorkers) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"sandbox: false is not supported in Cloudflare Workers. \" +\n\t\t\t\t\t\t\"Remove the sandbox: false option or use the Cloudflare sandbox runner.\",\n\t\t\t\t);\n\t\t\t}\n\t\t\tconsole.info(\n\t\t\t\t\"EmDash: Sandbox disabled (sandbox: false). \" +\n\t\t\t\t\t\"Sandboxed plugins will run in-process without isolation.\",\n\t\t\t);\n\t\t\tconst bypassedPlugins = await EmDashRuntime.loadBypassedPlugins(deps.sandboxedPluginEntries);\n\t\t\tfor (const plugin of bypassedPlugins) {\n\t\t\t\tallPipelinePlugins.push(plugin);\n\t\t\t\tbypassedPluginsList.push(plugin);\n\t\t\t\t// Respect plugin state: only enable if active or no record exists.\n\t\t\t\t// Plugins an admin previously disabled should stay disabled.\n\t\t\t\tconst status = pluginStates.get(plugin.id);\n\t\t\t\tif (status === undefined || status === \"active\") {\n\t\t\t\t\tenabledPlugins.add(plugin.id);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// In bypass mode, also load marketplace plugins from R2 as trusted\n\t\t// in-process plugins BEFORE pipeline creation. They need to be in the\n\t\t// pipeline to participate in hook dispatch.\n\t\tif (deps.sandboxBypassed && deps.config.marketplace && storage) {\n\t\t\tconst marketplaceBypassed = await EmDashRuntime.loadMarketplacePluginsBypassed(db, storage);\n\t\t\tfor (const plugin of marketplaceBypassed) {\n\t\t\t\tallPipelinePlugins.push(plugin);\n\t\t\t\tbypassedPluginsList.push(plugin);\n\t\t\t\tconst status = pluginStates.get(plugin.id);\n\t\t\t\tif (status === undefined || status === \"active\") {\n\t\t\t\t\tenabledPlugins.add(plugin.id);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Filter to currently enabled plugins for the initial pipeline\n\t\tconst enabledPluginList = allPipelinePlugins.filter((p) => enabledPlugins.has(p.id));\n\n\t\t// Create hook pipeline. getDb travels here (not just via the email\n\t\t// setContextFactory call below) so it survives rebuildHookPipeline(),\n\t\t// which reconstructs the factory from pipelineFactoryOptions. Without it,\n\t\t// toggling a plugin on an email-less deployment would silently revert\n\t\t// plugin contexts to the singleton db — re-breaking connection-backed\n\t\t// adapters. See #1622.\n\t\tconst pipelineFactoryOptions = {\n\t\t\tdb,\n\t\t\tgetDb: resolveDb,\n\t\t\tbeforeContentWrite: () => assertMediaUsageActivationWriteAllowed(resolveDb()),\n\t\t\tstorage: storage ?? undefined,\n\t\t\tsiteInfo,\n\t\t};\n\t\tconst pipeline = createHookPipeline(enabledPluginList, pipelineFactoryOptions);\n\n\t\t// Load sandboxed plugins (build-time, sandbox runner path)\n\t\tconst sandboxedPlugins = await phase(\"rt.sandbox\", \"Sandboxed plugins\", () =>\n\t\t\tEmDashRuntime.loadSandboxedPlugins(deps, db, storage, siteInfo),\n\t\t);\n\n\t\t// Cold-start: load marketplace- and registry-installed plugins from\n\t\t// site R2 via the sandbox runner. The two tiers only depend on the\n\t\t// sandbox phase above, not on each other, so when both are enabled\n\t\t// they run concurrently instead of paying two sequential loads.\n\t\t// In bypass mode marketplace plugins were already handled above.\n\t\tconst installedTierPhases: Promise<void>[] = [];\n\t\tif (deps.config.marketplace && storage && !deps.sandboxBypassed) {\n\t\t\tinstalledTierPhases.push(\n\t\t\t\tphase(\"rt.market\", \"Marketplace plugins\", () =>\n\t\t\t\t\tEmDashRuntime.loadInstalledSandboxedPlugins(\n\t\t\t\t\t\t\"marketplace\",\n\t\t\t\t\t\tdb,\n\t\t\t\t\t\tstorage,\n\t\t\t\t\t\tdeps,\n\t\t\t\t\t\tsandboxedPlugins,\n\t\t\t\t\t\tsiteInfo,\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\t// Cold-start: load registry-installed plugins from site R2\n\t\tif (deps.config.experimental?.registry && storage) {\n\t\t\tinstalledTierPhases.push(\n\t\t\t\tphase(\"rt.registry\", \"Registry plugins\", () =>\n\t\t\t\t\tEmDashRuntime.loadInstalledSandboxedPlugins(\n\t\t\t\t\t\t\"registry\",\n\t\t\t\t\t\tdb,\n\t\t\t\t\t\tstorage,\n\t\t\t\t\t\tdeps,\n\t\t\t\t\t\tsandboxedPlugins,\n\t\t\t\t\t\tsiteInfo,\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\tif (installedTierPhases.length > 0) {\n\t\t\tawait Promise.all(installedTierPhases);\n\t\t}\n\n\t\t// Initialize media providers\n\t\tconst mediaProviders = new Map<string, MediaProvider>();\n\t\tconst mediaProviderEntries = deps.mediaProviderEntries ?? [];\n\t\tconst providerContext: MediaProviderContext = { db, storage, getDb: resolveDb };\n\n\t\tfor (const entry of mediaProviderEntries) {\n\t\t\ttry {\n\t\t\t\tconst provider = entry.createProvider(providerContext);\n\t\t\t\tmediaProviders.set(entry.id, provider);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn(`Failed to initialize media provider \"${entry.id}\":`, error);\n\t\t\t}\n\t\t}\n\n\t\t// Resolve exclusive hooks — auto-select providers and sync with DB\n\t\tawait phase(\"rt.hooks\", \"Exclusive hook resolution\", () =>\n\t\t\tEmDashRuntime.resolveExclusiveHooks(pipeline, db, deps),\n\t\t);\n\n\t\t// ── Email pipeline ───────────────────────────────────────────────\n\t\t// The email pipeline orchestrates beforeSend → deliver → afterSend.\n\t\t// The dev console provider was registered above and will be auto-selected\n\t\t// by resolveExclusiveHooks if it's the sole email:deliver provider.\n\t\tconst emailPipeline = new EmailPipeline(pipeline);\n\n\t\t// Wire email send into sandbox runner (created earlier but without\n\t\t// email pipeline since it didn't exist yet)\n\t\tif (sandboxRunner) {\n\t\t\tsandboxRunner.setEmailSend((message, pluginId) => emailPipeline.send(message, pluginId));\n\t\t}\n\n\t\t// ── Cron system ──────────────────────────────────────────────────\n\t\t// Create executor with a hook dispatch function that uses the pipeline.\n\t\t// The callback reads from a mutable ref so that rebuildHookPipeline()\n\t\t// can swap the pipeline without reconstructing the CronExecutor.\n\t\tconst pipelineRef = { current: pipeline };\n\t\tconst invokeCronHook: InvokeCronHookFn = async (pluginId, event) => {\n\t\t\tconst result = await pipelineRef.current.invokeCronHook(pluginId, event);\n\t\t\tif (!result.success && result.error) {\n\t\t\t\tthrow result.error;\n\t\t\t}\n\t\t};\n\n\t\t// Wire email pipeline into context factory (independent of cron —\n\t\t// must not be inside the cron try/catch or ctx.email breaks when cron fails).\n\t\t// db/getDb were already set via pipelineFactoryOptions above; merge only\n\t\t// adds emailPipeline.\n\t\tpipeline.setContextFactory({ emailPipeline });\n\n\t\tlet cronExecutor: CronExecutor | null = null;\n\t\tlet cronScheduler: CronScheduler | null = null;\n\t\t// Populated with the constructed runtime just before this method returns,\n\t\t// so the timer scheduler's cleanup can route scheduled publishing through\n\t\t// the runtime wrapper (firing content:afterPublish hooks). The first tick\n\t\t// is ≥1s out, well after the synchronous assignment below.\n\t\tconst runtimeRef: { current: EmDashRuntime | null } = { current: null };\n\n\t\tawait phase(\"rt.cron\", \"Cron init (recovery deferred post-response)\", async () => {\n\t\t\ttry {\n\t\t\t\tcronExecutor = new CronExecutor(resolveDb, invokeCronHook);\n\t\t\t\t// Plugin schedules are always database-backed. On long-lived runtimes this\n\t\t\t\t// callback also wakes the timer; on Cloudflare the external Cron Trigger\n\t\t\t\t// drives execution, so rescheduling is intentionally a no-op.\n\t\t\t\tpipeline.setContextFactory({\n\t\t\t\t\tcronReschedule: () => cronScheduler?.reschedule(),\n\t\t\t\t});\n\n\t\t\t\t// Recover stale locks from previous crashes. Pure bookkeeping\n\t\t\t\t// against the _emdash_cron_tasks table — no request needs the\n\t\t\t\t// result — so we defer it past the response via after(). On\n\t\t\t\t// Cloudflare this goes into waitUntil (extending the worker\n\t\t\t\t// lifetime); on Node it's fire-and-forget (the process stays\n\t\t\t\t// up anyway). Saves one cold-start write per D1 isolate.\n\t\t\t\tconst executorForRecovery = cronExecutor;\n\t\t\t\tafter(async () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst recovered = await executorForRecovery.recoverStaleLocks();\n\t\t\t\t\t\tif (recovered > 0) {\n\t\t\t\t\t\t\tconsole.log(`[cron] Recovered ${recovered} stale task lock(s)`);\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t// Keep the `[cron]` prefix so a failure is easy to trace back\n\t\t\t\t\t\t// rather than surfacing as a generic deferred-task error.\n\t\t\t\t\t\tconsole.error(\"[cron] Failed to recover stale task locks:\", error);\n\t\t\t\t\t}\n\t\t\t\t});\n\n\t\t\t\t// The platform decides whether a long-lived timer heartbeat exists.\n\t\t\t\t// `createScheduler` is injected by the generated virtual:emdash/scheduler\n\t\t\t\t// module: a NodeCronScheduler factory on Node/Bun, or null on serverless\n\t\t\t\t// adapters (e.g. Cloudflare) where the Worker's `scheduled()` handler\n\t\t\t\t// drives runScheduledTasks() instead. No adapter check lives here.\n\t\t\t\tif (deps.createScheduler) {\n\t\t\t\t\tconst scheduler = deps.createScheduler(cronExecutor);\n\t\t\t\t\tcronScheduler = scheduler;\n\t\t\t\t\tconst runMediaUsageMaintenance = async () => {\n\t\t\t\t\t\tconst runtime = runtimeRef.current;\n\t\t\t\t\t\tif (runtime) {\n\t\t\t\t\t\t\tawait runtime.runScheduledMediaUsageTasks();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tawait runScheduledMediaUsageLane(db);\n\t\t\t\t\t\t}\n\t\t\t\t\t};\n\n\t\t\t\t\t// Run scheduled publishing and system cleanup alongside each tick.\n\t\t\t\t\t// Pass storage so cleanupPendingUploads can delete orphaned files.\n\t\t\t\t\tscheduler.setSystemCleanup(async () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t// Route through the runtime so content:afterPublish hooks fire.\n\t\t\t\t\t\t\t// Falls back to the raw handler if (improbably) the tick beats\n\t\t\t\t\t\t\t// the post-construction ref assignment.\n\t\t\t\t\t\t\tconst runtime = runtimeRef.current;\n\t\t\t\t\t\t\tif (runtime) {\n\t\t\t\t\t\t\t\tawait runtime.publishScheduled();\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tawait assertMediaUsageActivationWriteAllowed(db);\n\t\t\t\t\t\t\t\tawait publishDueContent(db);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tconsole.error(\"[scheduled-publish] Sweep failed:\", error);\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait runSystemCleanup(db, storage ?? undefined);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t// Non-fatal -- individual cleanup failures are already logged\n\t\t\t\t\t\t\t// by runSystemCleanup. This catches unexpected errors.\n\t\t\t\t\t\t\tconsole.error(\"[cleanup] System cleanup failed:\", error);\n\t\t\t\t\t\t}\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait runtimeRef.current?.syncPluginStorageIndexesOnce();\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tconsole.error(\"[plugins] Storage index sync failed:\", error);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Never throws; no-op unless scheduled backups are enabled and due.\n\t\t\t\t\t\tawait maybeRunScheduledBackup(db, storage ?? undefined);\n\t\t\t\t\t\tawait recordSchedulerHeartbeatSafely(db);\n\t\t\t\t\t\tif (!scheduler.setMediaUsageMaintenance) {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tawait runMediaUsageMaintenance();\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\tconsole.error(\"[media-usage] Scheduled maintenance failed:\", error);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t\tscheduler.setMediaUsageMaintenance?.(runMediaUsageMaintenance);\n\n\t\t\t\t\t// start() is void on the timer scheduler but the interface\n\t\t\t\t\t// allows a promise (alarm-backed schedulers); we don't block on it.\n\t\t\t\t\tvoid scheduler.start();\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.warn(\"[cron] Failed to initialize cron system:\", error);\n\t\t\t\t// Non-fatal — CMS works without cron\n\t\t\t}\n\t\t});\n\n\t\tconst runtime = new EmDashRuntime({\n\t\t\tdb,\n\t\t\tstorage,\n\t\t\t// Include bypassed sandboxed plugins in configuredPlugins so route\n\t\t\t// dispatch can find them under sandbox: false (they're treated as\n\t\t\t// trusted plugins for the duration of the bypass).\n\t\t\tconfiguredPlugins: [...deps.plugins, ...bypassedPluginsList],\n\t\t\tsandboxedPlugins,\n\t\t\tsandboxedPluginEntries: deps.sandboxedPluginEntries,\n\t\t\thooks: pipeline,\n\t\t\tenabledPlugins,\n\t\t\tpluginStates,\n\t\t\tconfig: deps.config,\n\t\t\tmediaProviders,\n\t\t\tmediaProviderEntries,\n\t\t\tcronExecutor,\n\t\t\tcronScheduler,\n\t\t\temailPipeline,\n\t\t\tallPipelinePlugins,\n\t\t\tpipelineFactoryOptions,\n\t\t\truntimeDeps: deps,\n\t\t\tpipelineRef,\n\t\t});\n\t\t// Hand the constructed instance to the scheduler-cleanup closure so the\n\t\t// timer-driven sweep can fire publish hooks (see runtimeRef above).\n\t\truntimeRef.current = runtime;\n\t\treturn runtime;\n\t}\n\n\t/**\n\t * Get a media provider by ID\n\t */\n\tgetMediaProvider(providerId: string): MediaProvider | undefined {\n\t\treturn this.mediaProviders.get(providerId);\n\t}\n\n\t/**\n\t * Get all media provider entries (for admin UI)\n\t */\n\tgetMediaProviderList(): Array<{\n\t\tid: string;\n\t\tname: string;\n\t\ticon?: string;\n\t\tcapabilities: MediaProviderCapabilities;\n\t}> {\n\t\treturn this.mediaProviderEntries.map((e) => ({\n\t\t\tid: e.id,\n\t\t\tname: e.name,\n\t\t\ticon: e.icon,\n\t\t\tcapabilities: e.capabilities,\n\t\t}));\n\t}\n\n\t/**\n\t * Get or create database instance\n\t */\n\tprivate static async getDatabase(deps: RuntimeDependencies): Promise<Kysely<Database>> {\n\t\t// Only use the per-request `ctx.db` when it's an isolated instance\n\t\t// (playground / DO preview). Plain D1 Sessions set `ctx.db` on every\n\t\t// anonymous request — if we captured one of those session-bound\n\t\t// Kyselys into the cached runtime, every request would accidentally\n\t\t// share one request's session. The configured `deps.createDialect`\n\t\t// path gives us a fresh singleton instead.\n\t\tconst ctx = getRequestContext();\n\t\tif (ctx?.dbIsIsolated && ctx.db) {\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- db in context is typed as unknown to avoid circular deps\n\t\t\treturn ctx.db as Kysely<Database>;\n\t\t}\n\n\t\tconst dbConfig = deps.config.database;\n\n\t\t// If no database configured in integration, try to get from loader\n\t\tif (!dbConfig) {\n\t\t\ttry {\n\t\t\t\treturn await getDb();\n\t\t\t} catch {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t\"EmDash database not configured. Either configure database in astro.config.mjs or use emdashLoader in live.config.ts\",\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tconst cacheKey = dbConfig.entrypoint;\n\n\t\t// Waiters poll the cache rather than sharing the initializing request's\n\t\t// promise: if the request that owns the init is cancelled mid-await\n\t\t// (e.g. client disconnect during cold migrations), a shared promise\n\t\t// never settles — and the owner's `finally` that would clear it never\n\t\t// runs — deadlocking every later request in the isolate. Prevention:\n\t\t// the in-flight init is anchored via after()/waitUntil so a cancelled\n\t\t// owner's init still completes and populates the cache. Net: a stale\n\t\t// lock is reclaimed after a deadline.\n\t\tconst holder = getDbHolder();\n\n\t\tconst throwIfBackingOff = () => {\n\t\t\tconst failure = holder.failures.get(cacheKey);\n\t\t\tif (!failure) return;\n\t\t\tif (Date.now() - failure.at < DB_INIT_FAILURE_BACKOFF_MS) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`Database initialization is backing off after a recent migration failure: ${failure.message}`,\n\t\t\t\t);\n\t\t\t}\n\t\t\t// Expired — drop it so the map only ever holds active backoffs.\n\t\t\tholder.failures.delete(cacheKey);\n\t\t};\n\t\tthrowIfBackingOff();\n\n\t\treturn initWithLock(\n\t\t\tholder.lock,\n\t\t\t() => holder.cache.get(cacheKey),\n\t\t\tasync (isCurrentClaim) => {\n\t\t\t\t// Re-check under the lock: a request that entered the wait loop\n\t\t\t\t// before the failure was recorded must not immediately re-run\n\t\t\t\t// the failing migration when the lock frees up.\n\t\t\t\tthrowIfBackingOff();\n\n\t\t\t\tconst dialect = deps.createDialect(dbConfig.config);\n\t\t\t\tconst db = new Kysely<Database>({ dialect, log: kyselyLogOption() });\n\n\t\t\t\ttry {\n\t\t\t\t\tawait enforceRuntimeMigrationPolicy(db, deps.migrationMode ?? \"auto\");\n\t\t\t\t} catch (error) {\n\t\t\t\t\t// Timing out behind another instance's in-flight migrations\n\t\t\t\t\t// is not a failure of OUR migration — the holder may just be\n\t\t\t\t\t// slow. Don't back off for it: the next request waits again\n\t\t\t\t\t// and init recovers the moment the holder finishes.\n\t\t\t\t\tif (\n\t\t\t\t\t\t!(error instanceof ConcurrentMigrationTimeoutError) &&\n\t\t\t\t\t\t!(error instanceof PendingMigrationsError)\n\t\t\t\t\t) {\n\t\t\t\t\t\tholder.failures.set(cacheKey, {\n\t\t\t\t\t\t\tat: Date.now(),\n\t\t\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\t// Every attempt builds a fresh dialect/pool; close it or each\n\t\t\t\t\t// failed init leaks its connection(s) (#1744 observed these\n\t\t\t\t\t// piling up as idle Postgres connections).\n\t\t\t\t\tawait db.destroy().catch(() => {});\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t\tholder.failures.delete(cacheKey);\n\n\t\t\t\t// Note: legacy installs may carry a stray `emdash:manifest_cache`\n\t\t\t\t// row in the options table from versions that persisted a JSON\n\t\t\t\t// manifest. The runtime no longer reads or writes it. We do not\n\t\t\t\t// proactively delete it: the row is a few hundred bytes of dead\n\t\t\t\t// weight and is never on the read path, whereas a one-shot\n\t\t\t\t// cleanup-flag check costs an extra `options.get()` on every\n\t\t\t\t// isolate cold boot forever. Cheaper to leave it.\n\n\t\t\t\t// This returns a migrated but possibly unseeded db; create() runs\n\t\t\t\t// the seed gate and applies the seed, batched with its other init\n\t\t\t\t// reads.\n\n\t\t\t\t// Publish only while still the current owner: a reclaimed slow\n\t\t\t\t// init must not flip the cached Kysely identity back after the\n\t\t\t\t// reclaimer has published its own. The unpublished instance is\n\t\t\t\t// still returned and fully valid for the request that built it.\n\t\t\t\tif (isCurrentClaim()) {\n\t\t\t\t\tholder.cache.set(cacheKey, db);\n\t\t\t\t}\n\t\t\t\treturn db;\n\t\t\t},\n\t\t\t{\n\t\t\t\tdeadlineMs: DB_INIT_DEADLINE_MS,\n\t\t\t\tanchor: (promise) => after(() => promise),\n\t\t\t},\n\t\t);\n\t}\n\n\t/**\n\t * Get or create storage instance\n\t */\n\tprivate static getStorage(deps: RuntimeDependencies): Storage | null {\n\t\tconst storageConfig = deps.config.storage;\n\t\tif (!storageConfig || !deps.createStorage) {\n\t\t\treturn null;\n\t\t}\n\n\t\tconst cacheKey = storageConfig.entrypoint;\n\t\tconst cached = storageCache.get(cacheKey);\n\t\tif (cached) {\n\t\t\treturn cached;\n\t\t}\n\n\t\tconst storage = deps.createStorage(storageConfig.config);\n\t\tstorageCache.set(cacheKey, storage);\n\t\treturn storage;\n\t}\n\n\t/**\n\t * Load sandboxed plugin entries as trusted in-process plugins.\n\t * Used by the sandbox: false debugging escape hatch.\n\t *\n\t * Imports each plugin's bundled ESM code via a data URL, adapts it\n\t * with adaptSandboxEntry, and returns ResolvedPlugin objects ready\n\t * to be merged into the pipeline plugin list.\n\t */\n\tprivate static async loadBypassedPlugins(\n\t\tentries: SandboxedPluginEntry[],\n\t): Promise<ResolvedPlugin[]> {\n\t\tconst { adaptSandboxEntry } = await import(\"./plugins/adapt-sandbox-entry.js\");\n\t\tconst plugins: ResolvedPlugin[] = [];\n\t\tfor (const entry of entries) {\n\t\t\ttry {\n\t\t\t\tconst dataUrl = `data:text/javascript;base64,${Buffer.from(entry.code).toString(\"base64\")}`;\n\t\t\t\t// eslint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- dynamic module from trusted bundle (built by plugin-cli); adaptSandboxEntry validates required fields.\n\t\t\t\tconst pluginModule = (await import(/* @vite-ignore */ dataUrl)) as Record<string, unknown>;\n\t\t\t\tconst pluginDef = (pluginModule.default ?? pluginModule) as Parameters<\n\t\t\t\t\ttypeof adaptSandboxEntry\n\t\t\t\t>[0];\n\t\t\t\t// PluginDescriptor.storage's TypeScript type is narrower than what\n\t\t\t\t// adaptSandboxEntry actually accepts at runtime — it copies indexes\n\t\t\t\t// through to PluginStorageConfig which supports composite indexes\n\t\t\t\t// (string[][]). Pass the raw entry.storage with a structural cast\n\t\t\t\t// to preserve composite index declarations.\n\t\t\t\t// eslint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- adaptSandboxEntry copies storage through to PluginStorageConfig which supports composite indexes\n\t\t\t\t// Preserve admin metadata so plugin-management APIs can derive\n\t\t\t\t// hasAdminPages / hasDashboardWidgets correctly. Without this,\n\t\t\t\t// the admin UI hides Configure links and dashboard widgets for\n\t\t\t\t// bypassed plugins even though they declared them.\n\t\t\t\t// SandboxedPluginEntry uses looser types than PluginDescriptor\n\t\t\t\t// (label?, size: string), so coerce to the descriptor shape.\n\t\t\t\tconst adminPages = entry.adminPages?.map((p) => ({\n\t\t\t\t\tpath: p.path,\n\t\t\t\t\tlabel: p.label ?? p.path,\n\t\t\t\t\ticon: p.icon,\n\t\t\t\t}));\n\t\t\t\tconst adminWidgets:\n\t\t\t\t\t| Array<{\n\t\t\t\t\t\t\tid: string;\n\t\t\t\t\t\t\ttitle?: string;\n\t\t\t\t\t\t\tsize?: \"full\" | \"half\" | \"third\";\n\t\t\t\t\t  }>\n\t\t\t\t\t| undefined = entry.adminWidgets?.map((w) => {\n\t\t\t\t\tconst size: \"full\" | \"half\" | \"third\" | undefined =\n\t\t\t\t\t\tw.size === \"full\" || w.size === \"half\" || w.size === \"third\" ? w.size : undefined;\n\t\t\t\t\treturn { id: w.id, title: w.title, size };\n\t\t\t\t});\n\t\t\t\tconst resolved = adaptSandboxEntry(pluginDef, {\n\t\t\t\t\tid: entry.id,\n\t\t\t\t\tversion: entry.version,\n\t\t\t\t\tentrypoint: \"\",\n\t\t\t\t\tcapabilities: entry.capabilities,\n\t\t\t\t\tallowedHosts: entry.allowedHosts,\n\t\t\t\t\t// eslint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- adaptSandboxEntry copies storage through\n\t\t\t\t\tstorage: entry.storage as never,\n\t\t\t\t\tadminPages,\n\t\t\t\t\tadminWidgets,\n\t\t\t\t\tsettingsSchema: entry.settingsSchema,\n\t\t\t\t\tportableTextBlocks: entry.portableTextBlocks,\n\t\t\t\t\tfieldWidgets: entry.fieldWidgets,\n\t\t\t\t});\n\t\t\t\tplugins.push(resolved);\n\t\t\t\tconsole.log(\n\t\t\t\t\t`EmDash: Loaded plugin ${entry.id}:${entry.version} in-process (sandbox bypassed)`,\n\t\t\t\t);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(`EmDash: Failed to load sandboxed plugin ${entry.id} in-process:`, error);\n\t\t\t}\n\t\t}\n\t\treturn plugins;\n\t}\n\n\t/**\n\t * Load sandboxed plugins using SandboxRunner\n\t */\n\tprivate static async loadSandboxedPlugins(\n\t\tdeps: RuntimeDependencies,\n\t\tdb: Kysely<Database>,\n\t\tmediaStorage?: Storage | null,\n\t\tsiteInfo?: {\n\t\t\tsiteName?: string;\n\t\t\tsiteUrl?: string;\n\t\t\tplatformUrl?: string;\n\t\t\tlocale?: string;\n\t\t\ttrailingSlash?: \"always\" | \"never\" | \"ignore\";\n\t\t},\n\t): Promise<Map<string, SandboxedPluginInstance>> {\n\t\t// Return cached plugins if already loaded\n\t\tif (sandboxedPluginCache.size > 0) {\n\t\t\treturn sandboxedPluginCache;\n\t\t}\n\n\t\t// Check if sandboxing is enabled\n\t\tif (!deps.sandboxEnabled) {\n\t\t\treturn sandboxedPluginCache;\n\t\t}\n\n\t\t// Create sandbox runner if not exists\n\t\tif (!sandboxRunner && deps.createSandboxRunner) {\n\t\t\tsandboxRunner = deps.createSandboxRunner(\n\t\t\t\tcreateSandboxRunnerOptions(\n\t\t\t\t\t{\n\t\t\t\t\t\tdb,\n\t\t\t\t\t\tbeforeContentWrite: () => assertMediaUsageActivationWriteAllowed(db),\n\t\t\t\t\t\tmediaStorage: mediaStorage\n\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\tupload: (opts) =>\n\t\t\t\t\t\t\t\t\t\tmediaStorage.upload({\n\t\t\t\t\t\t\t\t\t\t\tkey: opts.key,\n\t\t\t\t\t\t\t\t\t\t\tbody: opts.body,\n\t\t\t\t\t\t\t\t\t\t\tcontentType: opts.contentType,\n\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\tdelete: (key) => mediaStorage.delete(key),\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t},\n\t\t\t\t\tsiteInfo,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\n\t\tif (!sandboxRunner) {\n\t\t\treturn sandboxedPluginCache;\n\t\t}\n\n\t\t// Check if the runner is actually available (has required bindings).\n\t\t// Warn regardless of whether there are plugins to load, so operators\n\t\t// see the issue even if no marketplace plugins are installed yet.\n\t\tif (!sandboxRunner.isAvailable()) {\n\t\t\tconsole.warn(\n\t\t\t\t\"EmDash: Plugin sandbox is configured but not available on this platform. \" +\n\t\t\t\t\t\"Sandboxed plugins will not be loaded. \" +\n\t\t\t\t\t\"If using @premium-cms/sandbox-workerd/sandbox, ensure workerd is installed.\",\n\t\t\t);\n\t\t\treturn sandboxedPluginCache;\n\t\t}\n\n\t\tif (deps.sandboxedPluginEntries.length === 0) {\n\t\t\treturn sandboxedPluginCache;\n\t\t}\n\n\t\t// sandbox: false escape hatch is handled separately (before pipeline\n\t\t// creation) via loadBypassedPlugins. If we somehow reach here with the\n\t\t// flag set, just return — the plugins are already in the trusted pipeline.\n\t\tif (deps.sandboxBypassed) {\n\t\t\treturn sandboxedPluginCache;\n\t\t}\n\n\t\t// Load each sandboxed plugin via sandbox runner\n\t\tfor (const entry of deps.sandboxedPluginEntries) {\n\t\t\tconst pluginKey = `${entry.id}:${entry.version}`;\n\t\t\tif (sandboxedPluginCache.has(pluginKey)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\t// Build manifest from entry's declared config\n\t\t\t\tconst manifest: PluginManifest = {\n\t\t\t\t\tid: entry.id,\n\t\t\t\t\tversion: entry.version,\n\t\t\t\t\tcapabilities: entry.capabilities ?? [],\n\t\t\t\t\tallowedHosts: entry.allowedHosts ?? [],\n\t\t\t\t\tstorage: entry.storage ?? {},\n\t\t\t\t\thooks: entry.hooks ?? [],\n\t\t\t\t\troutes: entry.routes ?? [],\n\t\t\t\t\tadmin: {},\n\t\t\t\t\tmcp: entry.mcp,\n\t\t\t\t};\n\n\t\t\t\tconst plugin = await sandboxRunner.load(manifest, entry.code);\n\t\t\t\tsandboxedPluginCache.set(pluginKey, plugin);\n\t\t\t\tconsole.log(\n\t\t\t\t\t`EmDash: Loaded sandboxed plugin ${pluginKey} with capabilities: [${manifest.capabilities.join(\", \")}]`,\n\t\t\t\t);\n\n\t\t\t\tif (manifest.routes.length > 0) {\n\t\t\t\t\tconst routeMetaMap = new Map<string, RouteMeta>();\n\t\t\t\t\tfor (const routeEntry of manifest.routes) {\n\t\t\t\t\t\tconst normalized = normalizeManifestRoute(routeEntry);\n\t\t\t\t\t\trouteMetaMap.set(normalized.name, buildRouteMeta(normalized));\n\t\t\t\t\t}\n\t\t\t\t\tsandboxedRouteMetaCache.set(entry.id, routeMetaMap);\n\t\t\t\t} else {\n\t\t\t\t\tsandboxedRouteMetaCache.delete(entry.id);\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(`EmDash: Failed to load sandboxed plugin ${entry.id}:`, error);\n\t\t\t}\n\t\t}\n\n\t\treturn sandboxedPluginCache;\n\t}\n\n\t/**\n\t * Cold-start: load marketplace-installed plugins from site-local R2 storage\n\t *\n\t * Queries _plugin_state for source='marketplace' rows, fetches each bundle\n\t * from R2, and loads via SandboxRunner.\n\t */\n\t/**\n\t * Cold-start load of all active sandboxed plugins for one install\n\t * tier (marketplace or registry) from site-local R2.\n\t *\n\t * Mirrors {@link syncSandboxedSourcePlugins} but runs once at runtime\n\t * creation, before request traffic arrives; the sync method runs on\n\t * demand after install / update / uninstall handlers.\n\t */\n\tprivate static async loadInstalledSandboxedPlugins(\n\t\tsource: \"marketplace\" | \"registry\",\n\t\tdb: Kysely<Database>,\n\t\tstorage: Storage,\n\t\tdeps: RuntimeDependencies,\n\t\tcache: Map<string, SandboxedPluginInstance>,\n\t\tsiteInfo?: {\n\t\t\tsiteName?: string;\n\t\t\tsiteUrl?: string;\n\t\t\tplatformUrl?: string;\n\t\t\tlocale?: string;\n\t\t\ttrailingSlash?: \"always\" | \"never\" | \"ignore\";\n\t\t},\n\t): Promise<void> {\n\t\t// Ensure sandbox runner exists with media storage wired up.\n\t\t// (storage here is the media Storage adapter from the runtime.)\n\t\tif (!sandboxRunner && deps.createSandboxRunner) {\n\t\t\tsandboxRunner = deps.createSandboxRunner(\n\t\t\t\tcreateSandboxRunnerOptions(\n\t\t\t\t\t{\n\t\t\t\t\t\tdb,\n\t\t\t\t\t\tbeforeContentWrite: () => assertMediaUsageActivationWriteAllowed(db),\n\t\t\t\t\t\tmediaStorage: {\n\t\t\t\t\t\t\tupload: (opts) =>\n\t\t\t\t\t\t\t\tstorage.upload({\n\t\t\t\t\t\t\t\t\tkey: opts.key,\n\t\t\t\t\t\t\t\t\tbody: opts.body,\n\t\t\t\t\t\t\t\t\tcontentType: opts.contentType,\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\tdelete: (key) => storage.delete(key),\n\t\t\t\t\t\t},\n\t\t\t\t\t},\n\t\t\t\t\tsiteInfo,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t\t// In sandbox bypass mode, marketplace plugins are loaded in-process\n\t\t// BEFORE pipeline creation by EmDashRuntime.create(). Skip here.\n\t\tif (deps.sandboxBypassed) return;\n\n\t\tif (!sandboxRunner || !sandboxRunner.isAvailable()) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst keySet = source === \"marketplace\" ? marketplacePluginKeys : registryPluginKeys;\n\n\t\ttry {\n\t\t\tconst stateRepo = new PluginStateRepository(db);\n\t\t\tconst plugins =\n\t\t\t\tsource === \"marketplace\"\n\t\t\t\t\t? await stateRepo.getMarketplacePlugins()\n\t\t\t\t\t: await stateRepo.getRegistryPlugins();\n\n\t\t\tfor (const plugin of plugins) {\n\t\t\t\tif (plugin.status !== \"active\") continue;\n\n\t\t\t\t// Marketplace plugins record the live version in\n\t\t\t\t// `marketplaceVersion`; registry plugins use `version` directly.\n\t\t\t\tconst version =\n\t\t\t\t\tsource === \"marketplace\" ? (plugin.marketplaceVersion ?? plugin.version) : plugin.version;\n\t\t\t\tconst pluginKey = `${plugin.pluginId}:${version}`;\n\n\t\t\t\t// Skip if already loaded (shouldn't happen, but guard)\n\t\t\t\tif (cache.has(pluginKey)) continue;\n\n\t\t\t\ttry {\n\t\t\t\t\tconst bundle = await loadBundleFromR2(storage, plugin.pluginId, version, source);\n\t\t\t\t\tif (!bundle) {\n\t\t\t\t\t\tconsole.warn(`EmDash: ${source} plugin ${plugin.pluginId}@${version} not found in R2`);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst loaded = await sandboxRunner.load(bundle.manifest, bundle.backendCode);\n\t\t\t\t\tcache.set(pluginKey, loaded);\n\t\t\t\t\tkeySet.add(pluginKey);\n\n\t\t\t\t\t// Cache manifest admin config for getManifest()\n\t\t\t\t\tmarketplaceManifestCache.set(plugin.pluginId, {\n\t\t\t\t\t\tid: bundle.manifest.id,\n\t\t\t\t\t\tversion: bundle.manifest.version,\n\t\t\t\t\t\tadmin: bundle.manifest.admin,\n\t\t\t\t\t\tmcp: bundle.manifest.mcp,\n\t\t\t\t\t\tstorage: bundle.manifest.storage,\n\t\t\t\t\t});\n\n\t\t\t\t\t// Cache route metadata from manifest for auth decisions\n\t\t\t\t\tif (bundle.manifest.routes.length > 0) {\n\t\t\t\t\t\tconst routeMeta = new Map<string, RouteMeta>();\n\t\t\t\t\t\tfor (const entry of bundle.manifest.routes) {\n\t\t\t\t\t\t\tconst normalized = normalizeManifestRoute(entry);\n\t\t\t\t\t\t\trouteMeta.set(normalized.name, buildRouteMeta(normalized));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsandboxedRouteMetaCache.set(plugin.pluginId, routeMeta);\n\t\t\t\t\t}\n\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t`EmDash: Loaded ${source} plugin ${pluginKey} with capabilities: [${bundle.manifest.capabilities.join(\", \")}]`,\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(`EmDash: Failed to load ${source} plugin ${plugin.pluginId}:`, error);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// _plugin_state table may not exist yet (pre-migration)\n\t\t}\n\t}\n\n\t/**\n\t * Cold-start: load marketplace plugins in bypass mode (sandbox: false).\n\t *\n\t * Each active marketplace bundle is read, evaluated via data URL, adapted\n\t * with adaptSandboxEntry, and returned as a ResolvedPlugin. The caller is\n\t * responsible for merging these into allPipelinePlugins / configuredPlugins\n\t * BEFORE the hook pipeline is created, so hooks and routes register in\n\t * the trusted pipeline.\n\t *\n\t * Also caches manifest and route metadata so admin UI / getManifest() work.\n\t *\n\t * Returns ResolvedPlugins to be merged into the pipeline.\n\t */\n\tprivate static async loadMarketplacePluginsBypassed(\n\t\tdb: Kysely<Database>,\n\t\tstorage: Storage,\n\t): Promise<ResolvedPlugin[]> {\n\t\tconst resolved: ResolvedPlugin[] = [];\n\t\ttry {\n\t\t\tconst stateRepo = new PluginStateRepository(db);\n\t\t\tconst marketplacePlugins = await stateRepo.getMarketplacePlugins();\n\t\t\tif (marketplacePlugins.length === 0) return resolved;\n\n\t\t\tconsole.info(\n\t\t\t\t\"EmDash: Sandbox disabled (sandbox: false). \" +\n\t\t\t\t\t\"Marketplace plugins will run in-process without isolation.\",\n\t\t\t);\n\n\t\t\tconst { adaptSandboxEntry } = await import(\"./plugins/adapt-sandbox-entry.js\");\n\n\t\t\tfor (const plugin of marketplacePlugins) {\n\t\t\t\tif (plugin.status !== \"active\") continue;\n\t\t\t\tconst version = plugin.marketplaceVersion ?? plugin.version;\n\t\t\t\ttry {\n\t\t\t\t\tconst bundle = await loadBundleFromR2(storage, plugin.pluginId, version);\n\t\t\t\t\tif (!bundle) {\n\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t`EmDash: Marketplace plugin ${plugin.pluginId}@${version} not found in R2`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\t// Cache manifest and route metadata for admin UI and route auth\n\t\t\t\t\tmarketplaceManifestCache.set(plugin.pluginId, {\n\t\t\t\t\t\tid: bundle.manifest.id,\n\t\t\t\t\t\tversion: bundle.manifest.version,\n\t\t\t\t\t\tadmin: bundle.manifest.admin,\n\t\t\t\t\t\tmcp: bundle.manifest.mcp,\n\t\t\t\t\t\tstorage: bundle.manifest.storage,\n\t\t\t\t\t});\n\t\t\t\t\tif (bundle.manifest.routes.length > 0) {\n\t\t\t\t\t\tconst routeMeta = new Map<string, RouteMeta>();\n\t\t\t\t\t\tfor (const entry of bundle.manifest.routes) {\n\t\t\t\t\t\t\tconst normalized = normalizeManifestRoute(entry);\n\t\t\t\t\t\t\trouteMeta.set(normalized.name, buildRouteMeta(normalized));\n\t\t\t\t\t\t}\n\t\t\t\t\t\tsandboxedRouteMetaCache.set(plugin.pluginId, routeMeta);\n\t\t\t\t\t}\n\n\t\t\t\t\t// Evaluate the bundled ESM and adapt it as a trusted plugin\n\t\t\t\t\tconst dataUrl = `data:text/javascript;base64,${Buffer.from(bundle.backendCode).toString(\"base64\")}`;\n\t\t\t\t\t// eslint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- dynamic module from trusted bundle (built by plugin-cli); adaptSandboxEntry validates required fields.\n\t\t\t\t\tconst pluginModule = (await import(/* @vite-ignore */ dataUrl)) as Record<\n\t\t\t\t\t\tstring,\n\t\t\t\t\t\tunknown\n\t\t\t\t\t>;\n\t\t\t\t\tconst pluginDef = (pluginModule.default ?? pluginModule) as Parameters<\n\t\t\t\t\t\ttypeof adaptSandboxEntry\n\t\t\t\t\t>[0];\n\t\t\t\t\tconst adapted = adaptSandboxEntry(pluginDef, {\n\t\t\t\t\t\tid: bundle.manifest.id,\n\t\t\t\t\t\tversion: bundle.manifest.version,\n\t\t\t\t\t\tentrypoint: \"\",\n\t\t\t\t\t\tcapabilities: bundle.manifest.capabilities ?? [],\n\t\t\t\t\t\tallowedHosts: bundle.manifest.allowedHosts ?? [],\n\t\t\t\t\t\t// eslint-disable-next-line typescript-eslint/no-unsafe-type-assertion -- adaptSandboxEntry copies storage through\n\t\t\t\t\t\tstorage: (bundle.manifest.storage ?? {}) as never,\n\t\t\t\t\t\tadminPages: bundle.manifest.admin?.pages,\n\t\t\t\t\t\tadminWidgets: bundle.manifest.admin?.widgets?.map((w) => ({\n\t\t\t\t\t\t\tid: w.id,\n\t\t\t\t\t\t\ttitle: w.title,\n\t\t\t\t\t\t\tsize:\n\t\t\t\t\t\t\t\tw.size === \"full\" || w.size === \"half\" || w.size === \"third\" ? w.size : undefined,\n\t\t\t\t\t\t})),\n\t\t\t\t\t\tsettingsSchema: bundle.manifest.admin?.settingsSchema,\n\t\t\t\t\t});\n\t\t\t\t\tresolved.push(adapted);\n\t\t\t\t\tconsole.log(\n\t\t\t\t\t\t`EmDash: Loaded marketplace plugin ${plugin.pluginId}@${version} in-process (sandbox bypassed)`,\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`EmDash: Failed to load marketplace plugin ${plugin.pluginId} in-process:`,\n\t\t\t\t\t\terror,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\t\t} catch {\n\t\t\t// _plugin_state table may not exist yet\n\t\t}\n\t\treturn resolved;\n\t}\n\n\t/**\n\t * Resolve exclusive hook selections on startup.\n\t *\n\t * Delegates to the shared resolveExclusiveHooks() in hooks.ts.\n\t * The runtime version considers all pipeline providers as \"active\" since\n\t * the pipeline was already built from only active/enabled plugins.\n\t */\n\tprivate static async resolveExclusiveHooks(\n\t\tpipeline: HookPipeline,\n\t\tdb: Kysely<Database>,\n\t\tdeps: RuntimeDependencies,\n\t): Promise<void> {\n\t\tconst exclusiveHookNames = pipeline.getRegisteredExclusiveHooks();\n\t\tif (exclusiveHookNames.length === 0) return;\n\n\t\tlet optionsRepo: OptionsRepository;\n\t\ttry {\n\t\t\toptionsRepo = new OptionsRepository(db);\n\t\t} catch {\n\t\t\treturn; // Options table may not exist yet\n\t\t}\n\n\t\t// Build preferred hints from sandboxed plugin entries\n\t\tconst preferredHints = new Map<string, string[]>();\n\t\tfor (const entry of deps.sandboxedPluginEntries) {\n\t\t\tif (entry.preferred && entry.preferred.length > 0) {\n\t\t\t\tpreferredHints.set(entry.id, entry.preferred);\n\t\t\t}\n\t\t}\n\n\t\t// The pipeline was created from only enabled plugins, so all providers\n\t\t// in it are active. The isActive check always returns true.\n\t\tawait resolveExclusiveHooksShared({\n\t\t\tpipeline,\n\t\t\tisActive: () => true,\n\t\t\tgetOption: (key) => optionsRepo.get<string>(key),\n\t\t\tgetOptions: (keys) => optionsRepo.getMany<string>(keys),\n\t\t\tsetOption: (key, value) => optionsRepo.set(key, value),\n\t\t\tdeleteOption: async (key) => {\n\t\t\t\tawait optionsRepo.delete(key);\n\t\t\t},\n\t\t\tpreferredHints,\n\t\t});\n\t}\n\n\t// =========================================================================\n\t// Manifest\n\t// =========================================================================\n\n\t/**\n\t * Build the admin manifest from the live database.\n\t *\n\t * Used by the admin UI (sidebar collections, content editor field\n\t * dispatch, manifest endpoint) and by WordPress import — it's never\n\t * read on a public request, so this isn't on any anonymous hot path.\n\t *\n\t * No cross-request cache. The previous worker-isolate cache produced\n\t * a class of cross-isolate staleness bugs (#776, #873, #876, #877)\n\t * because Cloudflare Workers keeps multiple warm isolates per region\n\t * and there's no fan-out primitive to invalidate them in step. The\n\t * cache existed to amortize an N+1 schema query pattern; now that\n\t * `listCollectionsWithFields()` does the same work in two queries,\n\t * the rebuild is fast enough to pay on every admin request.\n\t *\n\t * Within a single request, `requestCached` deduplicates concurrent\n\t * callers (the manifest endpoint and an admin SSR template, say).\n\t */\n\tgetManifest(): Promise<EmDashManifest> {\n\t\treturn requestCached(\"emdash:manifest\", () => this._buildManifest());\n\t}\n\n\t/**\n\t * Build the manifest from the database.\n\t *\n\t * Constant query shapes via `listCollectionsWithFields()` — one query\n\t * for collections, one batched query for fields (chunked at\n\t * `SQL_BATCH_SIZE` collection IDs to stay under D1's bound-parameter\n\t * limit). Typical sites stay well under the chunk threshold, so this\n\t * is two queries in practice; never N+1.\n\t */\n\tprivate async _buildManifest(): Promise<EmDashManifest> {\n\t\t// Build collections from database.\n\t\t// Use this.db (ALS-aware getter) so playground mode picks up the\n\t\t// per-session DO database instead of the hardcoded singleton.\n\t\tconst manifestCollections: Record<string, ManifestCollection> = {};\n\t\ttry {\n\t\t\tconst registry = new SchemaRegistry(this.db);\n\t\t\tconst dbCollections = await registry.listCollectionsWithFields();\n\t\t\tfor (const collection of dbCollections) {\n\t\t\t\tconst fields: Record<\n\t\t\t\t\tstring,\n\t\t\t\t\t{\n\t\t\t\t\t\tkind: string;\n\t\t\t\t\t\tlabel?: string;\n\t\t\t\t\t\trequired?: boolean;\n\t\t\t\t\t\twidget?: string;\n\t\t\t\t\t\t// Two shapes: legacy enum-style `[{ value, label }]` for select widgets,\n\t\t\t\t\t\t// or arbitrary `Record<string, unknown>` for plugin field widgets that\n\t\t\t\t\t\t// need per-field config (e.g. a checkbox grid receiving its column defs).\n\t\t\t\t\t\toptions?: Array<{ value: string; label: string }> | Record<string, unknown>;\n\t\t\t\t\t\tid?: string;\n\t\t\t\t\t\tvalidation?: Record<string, unknown>;\n\t\t\t\t\t\treadOnly?: boolean;\n\t\t\t\t\t\tvisibility?: \"create\" | \"edit\";\n\t\t\t\t\t\toptionsUrl?: string;\n\t\t\t\t\t}\n\t\t\t\t> = {};\n\n\t\t\t\tfor (const field of collection.fields) {\n\t\t\t\t\tconst entry: (typeof fields)[string] = {\n\t\t\t\t\t\tkind: FIELD_TYPE_TO_KIND[field.type] ?? \"string\",\n\t\t\t\t\t\tlabel: field.label,\n\t\t\t\t\t\trequired: field.required,\n\t\t\t\t\t};\n\t\t\t\t\t// Generic editor-UX flags carried in the field's options bag:\n\t\t\t\t\t// `readOnly` renders the input disabled; `visibility` restricts the\n\t\t\t\t\t// field to the create or edit form. Promoted to top-level manifest\n\t\t\t\t\t// props so they survive the select-enum options overwrite below.\n\t\t\t\t\tconst fieldOpts =\n\t\t\t\t\t\tfield.options && !Array.isArray(field.options)\n\t\t\t\t\t\t\t? (field.options as Record<string, unknown>)\n\t\t\t\t\t\t\t: undefined;\n\t\t\t\t\tif (fieldOpts?.readOnly === true) entry.readOnly = true;\n\t\t\t\t\tif (fieldOpts?.visibility === \"create\" || fieldOpts?.visibility === \"edit\")\n\t\t\t\t\t\tentry.visibility = fieldOpts.visibility;\n\t\t\t\t\t// `optionsUrl`: a string field rendered as a select whose choices the\n\t\t\t\t\t// admin fetches from this (same-origin) URL — `{ options: [{value,label}] }`.\n\t\t\t\t\tif (typeof fieldOpts?.optionsUrl === \"string\" && fieldOpts.optionsUrl.startsWith(\"/\"))\n\t\t\t\t\t\tentry.optionsUrl = fieldOpts.optionsUrl;\n\t\t\t\t\t// Always include the field's database ID so the admin can forward it\n\t\t\t\t\t// to upload/media-list API calls for MIME allowlist widening.\n\t\t\t\t\tentry.id = field.id;\n\t\t\t\t\tif (field.widget) entry.widget = field.widget;\n\t\t\t\t\t// Plugin field widgets read their per-field config from `field.options`,\n\t\t\t\t\t// which the seed schema types as `Record<string, unknown>`. Pass it\n\t\t\t\t\t// through to the manifest so plugin widgets in the admin SPA receive it.\n\t\t\t\t\tif (field.options) {\n\t\t\t\t\t\tentry.options = field.options;\n\t\t\t\t\t}\n\t\t\t\t\t// Legacy: select/multiSelect enum options live on `field.validation.options`.\n\t\t\t\t\t// Wins over `field.options` to preserve existing behavior for enum widgets.\n\t\t\t\t\tif (field.validation?.options) {\n\t\t\t\t\t\tentry.options = field.validation.options.map((v) => ({\n\t\t\t\t\t\t\tvalue: v,\n\t\t\t\t\t\t\tlabel: v.charAt(0).toUpperCase() + v.slice(1),\n\t\t\t\t\t\t}));\n\t\t\t\t\t}\n\t\t\t\t\t// Include full validation for repeater fields (subFields, minItems, maxItems)\n\t\t\t\t\t// and for file/image fields (allowedMimeTypes).\n\t\t\t\t\tif (\n\t\t\t\t\t\t(field.type === \"repeater\" || field.type === \"file\" || field.type === \"image\") &&\n\t\t\t\t\t\tfield.validation\n\t\t\t\t\t) {\n\t\t\t\t\t\tentry.validation = { ...field.validation };\n\t\t\t\t\t}\n\t\t\t\t\tfields[field.slug] = entry;\n\t\t\t\t}\n\n\t\t\t\tconst configuredListColumns = collection.admin?.listColumns ?? [];\n\t\t\t\tconst fieldTypes = new Map(collection.fields.map((field) => [field.slug, field.type]));\n\t\t\t\tconst listColumns: string[] = [];\n\t\t\t\tfor (const slug of configuredListColumns) {\n\t\t\t\t\tif (listColumns.includes(slug)) continue;\n\t\t\t\t\tconst fieldType = fieldTypes.get(slug);\n\t\t\t\t\tif (!fieldType || !LIST_COLUMN_FIELD_TYPES.has(fieldType)) {\n\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t`EmDash: Ignoring unsupported or unknown list column \"${slug}\" in collection \"${collection.slug}\".`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\t\t\t\t\tif (listColumns.length >= MAX_COLLECTION_LIST_COLUMNS) {\n\t\t\t\t\t\tconsole.warn(\n\t\t\t\t\t\t\t`EmDash: Collection \"${collection.slug}\" declares more than ${MAX_COLLECTION_LIST_COLUMNS} list columns; extra columns are ignored.`,\n\t\t\t\t\t\t);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tlistColumns.push(slug);\n\t\t\t\t}\n\n\t\t\t\tmanifestCollections[collection.slug] = {\n\t\t\t\t\tlabel: collection.label,\n\t\t\t\t\tlabelSingular: collection.labelSingular || collection.label,\n\t\t\t\t\t// Git-backed entries have no drafts/revisions/scheduling: the repo\n\t\t\t\t\t// history is the history, and every save is a commit.\n\t\t\t\t\tsupports:\n\t\t\t\t\t\tcollection.storage === \"git\"\n\t\t\t\t\t\t\t? (collection.supports || []).filter((s) => s === \"seo\" || s === \"search\" || s === \"preview\")\n\t\t\t\t\t\t\t: collection.supports || [],\n\t\t\t\t\t...(collection.storage === \"git\" ? { storage: \"git\" as const } : {}),\n\t\t\t\t\thasSeo: collection.hasSeo,\n\t\t\t\t\turlPattern: collection.urlPattern,\n\t\t\t\t\troutable: collection.routable !== false,\n\t\t\t\t\ttitleField: collection.titleField,\n\t\t\t\t\tdateField: collection.dateField,\n\t\t\t\t\t...(collection.hidden ? { hidden: true } : {}),\n\t\t\t\t\tlistColumns: listColumns.length > 0 ? listColumns : undefined,\n\t\t\t\t\tfields,\n\t\t\t\t};\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.debug(\"EmDash: Could not load database collections:\", error);\n\t\t}\n\n\t\t// Build plugins manifest\n\t\tconst manifestPlugins: Record<\n\t\t\tstring,\n\t\t\t{\n\t\t\t\tversion?: string;\n\t\t\t\tenabled?: boolean;\n\t\t\t\tsandboxed?: boolean;\n\t\t\t\tadminMode?: \"react\" | \"blocks\" | \"none\";\n\t\t\t\tadminPages?: Array<{ path: string; label?: string; icon?: string }>;\n\t\t\t\tdashboardWidgets?: Array<{\n\t\t\t\t\tid: string;\n\t\t\t\t\ttitle?: string;\n\t\t\t\t\tsize?: string;\n\t\t\t\t}>;\n\t\t\t\tportableTextBlocks?: Array<{\n\t\t\t\t\ttype: string;\n\t\t\t\t\tlabel: string;\n\t\t\t\t\ticon?: string;\n\t\t\t\t\tdescription?: string;\n\t\t\t\t\tplaceholder?: string;\n\t\t\t\t\tfields?: Element[];\n\t\t\t\t\tcategory?: string;\n\t\t\t\t}>;\n\t\t\t\tfieldWidgets?: Array<{\n\t\t\t\t\tname: string;\n\t\t\t\t\tlabel: string;\n\t\t\t\t\tfieldTypes: string[];\n\t\t\t\t\telements?: Element[];\n\t\t\t\t}>;\n\t\t\t}\n\t\t> = {};\n\n\t\tfor (const plugin of this.configuredPlugins) {\n\t\t\tconst status = this.pluginStates.get(plugin.id);\n\t\t\tconst enabled = status === undefined || status === \"active\";\n\n\t\t\t// Determine admin mode: has admin entry → react, has pages/widgets → blocks, else none\n\t\t\tconst hasAdminEntry = !!plugin.admin?.entry;\n\t\t\tconst hasAdminPages = (plugin.admin?.pages?.length ?? 0) > 0;\n\t\t\tconst hasWidgets = (plugin.admin?.widgets?.length ?? 0) > 0;\n\t\t\tlet adminMode: \"react\" | \"blocks\" | \"none\" = \"none\";\n\t\t\tif (hasAdminEntry) {\n\t\t\t\tadminMode = \"react\";\n\t\t\t} else if (hasAdminPages || hasWidgets) {\n\t\t\t\tadminMode = \"blocks\";\n\t\t\t}\n\n\t\t\tmanifestPlugins[plugin.id] = {\n\t\t\t\tversion: plugin.version,\n\t\t\t\tenabled,\n\t\t\t\tadminMode,\n\t\t\t\tadminPages: plugin.admin?.pages ?? [],\n\t\t\t\tdashboardWidgets: plugin.admin?.widgets ?? [],\n\t\t\t\tportableTextBlocks: plugin.admin?.portableTextBlocks,\n\t\t\t\tfieldWidgets: plugin.admin?.fieldWidgets,\n\t\t\t};\n\t\t}\n\n\t\t// Add sandboxed plugins (use entries for admin config)\n\t\tfor (const entry of this.sandboxedPluginEntries) {\n\t\t\tconst status = this.pluginStates.get(entry.id);\n\t\t\tconst enabled = status === undefined || status === \"active\";\n\n\t\t\tconst hasAdminPages = (entry.adminPages?.length ?? 0) > 0;\n\t\t\tconst hasWidgets = (entry.adminWidgets?.length ?? 0) > 0;\n\n\t\t\tmanifestPlugins[entry.id] = {\n\t\t\t\tversion: entry.version,\n\t\t\t\tenabled,\n\t\t\t\tsandboxed: true,\n\t\t\t\t// `adminMode` reflects only admin pages/widgets. A plugin can\n\t\t\t\t// contribute portableTextBlocks/fieldWidgets with adminMode \"none\" —\n\t\t\t\t// the admin reads those from the manifest regardless, so don't gate\n\t\t\t\t// admin contributions on `adminMode`.\n\t\t\t\tadminMode: hasAdminPages || hasWidgets ? \"blocks\" : \"none\",\n\t\t\t\tadminPages: entry.adminPages ?? [],\n\t\t\t\tdashboardWidgets: entry.adminWidgets ?? [],\n\t\t\t\tportableTextBlocks: entry.portableTextBlocks,\n\t\t\t\tfieldWidgets: entry.fieldWidgets,\n\t\t\t};\n\t\t}\n\n\t\t// Add marketplace-installed plugins (dynamically loaded from R2)\n\t\tfor (const [pluginId, meta] of marketplaceManifestCache) {\n\t\t\t// Skip if already included from build-time config\n\t\t\tif (manifestPlugins[pluginId]) continue;\n\n\t\t\tconst status = this.pluginStates.get(pluginId);\n\t\t\tconst enabled = status === \"active\";\n\n\t\t\tconst pages = meta.admin?.pages;\n\t\t\tconst widgets = meta.admin?.widgets;\n\t\t\tconst hasAdminPages = (pages?.length ?? 0) > 0;\n\t\t\tconst hasWidgets = (widgets?.length ?? 0) > 0;\n\n\t\t\tmanifestPlugins[pluginId] = {\n\t\t\t\tversion: meta.version,\n\t\t\t\tenabled,\n\t\t\t\tsandboxed: true,\n\t\t\t\tadminMode: hasAdminPages || hasWidgets ? \"blocks\" : \"none\",\n\t\t\t\tadminPages: pages ?? [],\n\t\t\t\tdashboardWidgets: widgets ?? [],\n\t\t\t};\n\t\t}\n\n\t\t// Build taxonomies from database\n\t\tlet manifestTaxonomies: Array<{\n\t\t\tid: string;\n\t\t\tname: string;\n\t\t\tlabel: string;\n\t\t\tlabelSingular?: string;\n\t\t\thierarchical: boolean;\n\t\t\tcollections: string[];\n\t\t\tlocale: string;\n\t\t\ttranslationGroup: string;\n\t\t}> = [];\n\t\tlet taxonomyDefinitionLocales: string[] = [];\n\t\ttry {\n\t\t\tconst rows = await this.db\n\t\t\t\t.selectFrom(\"_emdash_taxonomy_defs\")\n\t\t\t\t.selectAll()\n\t\t\t\t.orderBy(\"name\")\n\t\t\t\t.execute();\n\t\t\ttaxonomyDefinitionLocales = rows.map((row) => row.locale);\n\t\t\tmanifestTaxonomies = rows.map((row) => ({\n\t\t\t\tid: row.id,\n\t\t\t\tname: row.name,\n\t\t\t\tlabel: row.label,\n\t\t\t\tlabelSingular: row.label_singular ?? undefined,\n\t\t\t\thierarchical: row.hierarchical === 1,\n\t\t\t\tcollections: parseStringArray(row.collections).toSorted(),\n\t\t\t\tlocale: row.locale,\n\t\t\t\ttranslationGroup: row.translation_group ?? row.id,\n\t\t\t}));\n\t\t} catch (error) {\n\t\t\tconsole.debug(\"EmDash: Could not load taxonomy definitions:\", error);\n\t\t}\n\n\t\ttry {\n\t\t\tconst configuredLocales = virtualConfig?.i18n?.locales ?? getI18nConfig()?.locales ?? [];\n\t\t\tawait warnAboutUnconfiguredTaxonomyLocales(\n\t\t\t\tthis.db,\n\t\t\t\tconfiguredLocales,\n\t\t\t\ttaxonomyDefinitionLocales,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconsole.warn(\"[i18n] taxonomy locale diagnostic failed:\", error);\n\t\t}\n\n\t\t// Build manifest hash\n\t\tconst manifestHash = await hashString(\n\t\t\tJSON.stringify(manifestCollections) +\n\t\t\t\tJSON.stringify(manifestPlugins) +\n\t\t\t\tJSON.stringify(manifestTaxonomies),\n\t\t);\n\n\t\t// Determine auth mode\n\t\tconst authMode = getAuthMode(this.config);\n\t\tconst authModeValue = authMode.type === \"external\" ? authMode.providerType : \"passkey\";\n\n\t\t// Include i18n config if enabled (read from virtual module to avoid SSR module singleton mismatch)\n\t\tconst i18nConfig = virtualConfig?.i18n ?? getI18nConfig();\n\t\tconst i18n =\n\t\t\ti18nConfig && i18nConfig.locales && i18nConfig.locales.length > 1\n\t\t\t\t? { defaultLocale: i18nConfig.defaultLocale, locales: i18nConfig.locales }\n\t\t\t\t: undefined;\n\n\t\t// Normalize the experimental registry config for browser consumption.\n\t\t// Validation errors here surface as 500s from the manifest endpoint\n\t\t// rather than being silently dropped -- a misconfigured registry\n\t\t// should be loud, not invisible.\n\t\tconst registry = normalizeRegistryConfig(this.config.experimental?.registry) ?? undefined;\n\n\t\treturn {\n\t\t\tversion: VERSION,\n\t\t\tcommit: COMMIT,\n\t\t\tastroVersion: this.config.astroVersion,\n\t\t\thash: manifestHash,\n\t\t\tcollections: manifestCollections,\n\t\t\tplugins: manifestPlugins,\n\t\t\ttaxonomies: manifestTaxonomies,\n\t\t\tauthMode: authModeValue,\n\t\t\ti18n,\n\t\t\tcontentLocale: {\n\t\t\t\tdefaultLocale: i18nConfig?.defaultLocale ?? \"en\",\n\t\t\t\timplicit: i18nConfig === null,\n\t\t\t},\n\t\t\tmarketplace: !!this.config.marketplace,\n\t\t\tregistry,\n\t\t};\n\t}\n\n\t/**\n\t * Verify and repair FTS indexes on demand. Runs at most once per worker\n\t * lifetime.\n\t *\n\t * Originally called from `EmDashRuntime.create()`, but on a busy D1 link\n\t * (e.g. SIN replica ~80-150ms per query) it added ~1.5s to every cold\n\t * start for a modest-sized site — more than every other init phase\n\t * combined. Anonymous public reads never touch the search write path,\n\t * so the cost isn't paid back for the vast majority of requests.\n\t *\n\t * Instead, search endpoints call this lazily: the first request that\n\t * actually needs the index pays the verify cost (usually fast — no\n\t * rebuild needed), everyone else runs cold-free.\n\t *\n\t * Uses the runtime's singleton database (`this._db`) rather than the\n\t * request-scoped DB. Verify reads only, but `rebuildIndex` writes, and\n\t * a GET search request on D1 carries a `first-unconstrained` session\n\t * that's free to route at a read replica — unsafe for writes. The\n\t * singleton always goes through the default binding, which the D1\n\t * adapter will promote to `first-primary` for write statements.\n\t *\n\t * Safe to call concurrently: repeated callers share the same in-flight\n\t * promise. Errors are swallowed internally so callers don't need to\n\t * defend against FTS not existing yet (pre-setup).\n\t */\n\tasync ensureSearchHealthy(): Promise<void> {\n\t\t// Non-SQLite has no FTS to verify; the check is a cheap synchronous\n\t\t// branch, no need to cache it.\n\t\tif (!isSqlite(this._db)) return;\n\t\ttry {\n\t\t\tawait singleFlightCached(\n\t\t\t\tthis._searchHealthCache,\n\t\t\t\tasync () => {\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst ftsManager = new FTSManager(this._db);\n\t\t\t\t\t\tconst repaired = await ftsManager.verifyAndRepairAll();\n\t\t\t\t\t\tif (repaired > 0) {\n\t\t\t\t\t\t\tconsole.log(`Repaired ${repaired} corrupted FTS index(es)`);\n\t\t\t\t\t\t}\n\t\t\t\t\t} catch {\n\t\t\t\t\t\t// FTS tables may not exist yet (pre-setup). Non-fatal — cache\n\t\t\t\t\t\t// the \"checked\" state regardless so we don't re-scan.\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\t{ anchor: (promise) => after(() => promise), ownerTimeoutMs: 30_000 },\n\t\t\t);\n\t\t} catch {\n\t\t\t// This check is best-effort and must never fail the calling request.\n\t\t\t// The inner body already swallows verify errors; this guards the\n\t\t\t// outer failure modes (owner timeout, waiter give-up) so a slow FTS\n\t\t\t// scan degrades to \"unverified\", not a 500 on admin/search routes.\n\t\t}\n\t}\n\n\t// =========================================================================\n\t// Content Handlers\n\t// =========================================================================\n\n\t/**\n\t * The git-backed store for a collection whose entries live in the site's\n\t * repo (`storage: \"git\"`), or null for database collections. Throws when\n\t * the collection is git-backed but GitHub isn't connected yet.\n\t */\n\tprivate async gitStoreFor(collection: string): Promise<GitContentStore | null> {\n\t\tconst def = await this.schemaRegistry.getCollection(collection);\n\t\tif (!def || def.storage !== \"git\") return null;\n\t\tconst conn = await gitConnection(this.db);\n\t\tif (!conn) {\n\t\t\tthrow new GitStoreError(\n\t\t\t\t\"This collection is stored in git — connect GitHub in Settings → General first.\",\n\t\t\t\t\"NOT_CONNECTED\",\n\t\t\t\t409,\n\t\t\t);\n\t\t}\n\t\treturn new GitContentStore(conn, collection);\n\t}\n\n\tprivate gitError(error: unknown, code: string, message: string) {\n\t\tif (error instanceof GitStoreError) {\n\t\t\treturn { success: false as const, error: { code: error.code, message: error.message } };\n\t\t}\n\t\tconsole.error(message, error);\n\t\treturn { success: false as const, error: { code, message } };\n\t}\n\n\tasync handleContentList(\n\t\tcollection: string,\n\t\tparams: {\n\t\t\tcursor?: string;\n\t\t\tlimit?: number;\n\t\t\tstatus?: string;\n\t\t\torderBy?: string;\n\t\t\torder?: \"asc\" | \"desc\";\n\t\t\tlocale?: string;\n\t\t\tq?: string;\n\t\t\tauthorId?: string;\n\t\t\tdateField?: ContentDateField;\n\t\t\tdateFrom?: string;\n\t\t\tdateTo?: string;\n\t\t\tbylines?: string[];\n\t\t\tbylinesNone?: boolean;\n\t\t\tincludeInferredBylines?: boolean;\n\t\t\tfieldFilters?: ContentFieldFilters;\n\t\t},\n\t) {\n\t\ttry {\n\t\t\tconst git = await this.gitStoreFor(collection);\n\t\t\tif (git) {\n\t\t\t\tlet items = await git.list();\n\t\t\t\tif (params.status) items = items.filter((i) => i.status === params.status);\n\t\t\t\tif (params.q) {\n\t\t\t\t\tconst q = params.q.toLowerCase();\n\t\t\t\t\titems = items.filter((i) => JSON.stringify(i.data).toLowerCase().includes(q) || (i.slug ?? \"\").includes(q));\n\t\t\t\t}\n\t\t\t\treturn { success: true as const, data: { items, nextCursor: undefined, total: items.length } };\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn this.gitError(error, \"CONTENT_LIST_ERROR\", \"Failed to list content\");\n\t\t}\n\t\treturn handleContentList(this.db, collection, params);\n\t}\n\n\tasync handleContentAuthors(collection: string) {\n\t\treturn handleContentAuthors(this.db, collection);\n\t}\n\n\tasync handleContentGet(collection: string, id: string, locale?: string) {\n\t\ttry {\n\t\t\tconst git = await this.gitStoreFor(collection);\n\t\t\tif (git) {\n\t\t\t\tconst item = await git.get(id);\n\t\t\t\tif (!item)\n\t\t\t\t\treturn { success: false as const, error: { code: \"NOT_FOUND\", message: `Content item not found: ${id}` } };\n\t\t\t\treturn { success: true as const, data: { item, _rev: item.updatedAt } };\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn this.gitError(error, \"CONTENT_GET_ERROR\", \"Failed to get content\");\n\t\t}\n\t\tconst result = await handleContentGet(this.db, collection, id, locale);\n\t\treturn this.hydrateDraftData(result);\n\t}\n\n\tasync handleContentGetIncludingTrashed(collection: string, id: string, locale?: string) {\n\t\tconst result = await handleContentGetIncludingTrashed(this.db, collection, id, locale);\n\t\treturn this.hydrateDraftData(result);\n\t}\n\n\t/**\n\t * If the response item has a `draftRevisionId`, replace `item.data` with\n\t * the draft revision's data and expose the original published values as\n\t * `liveData`. This makes the content_get / content_update round-trip\n\t * intuitive — read returns the latest content the caller has saved\n\t * (their pending draft), with the previously-published values still\n\t * accessible for compare-style flows.\n\t *\n\t * No-op when no draft exists or the response is an error.\n\t */\n\tprivate async hydrateDraftData<T>(result: T): Promise<T> {\n\t\tif (!result || typeof result !== \"object\") return result;\n\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- shape probed below\n\t\tconst r = result as {\n\t\t\tsuccess?: boolean;\n\t\t\tdata?: { item?: Record<string, unknown> };\n\t\t};\n\t\tif (!r.success || !r.data?.item) return result;\n\t\tconst item = r.data.item;\n\t\tconst draftRevisionId = typeof item.draftRevisionId === \"string\" ? item.draftRevisionId : null;\n\t\tif (!draftRevisionId) return result;\n\t\ttry {\n\t\t\tconst revision = await new RevisionRepository(this.db).findById(draftRevisionId);\n\t\t\tif (!revision) return result;\n\t\t\tconst liveData =\n\t\t\t\titem.data && typeof item.data === \"object\"\n\t\t\t\t\t? // eslint-disable-next-line typescript/no-unsafe-type-assertion -- narrowed to object above\n\t\t\t\t\t\t(item.data as Record<string, unknown>)\n\t\t\t\t\t: {};\n\t\t\t// Strip leading-underscore keys (`_slug`, `_rev`, etc.) from the\n\t\t\t// revision data — those are handler-internal markers and don't\n\t\t\t// belong in the surfaced `data` field. Match syncDataColumns at\n\t\t\t// content.ts:~1119.\n\t\t\tconst revisionData: Record<string, unknown> = {};\n\t\t\tfor (const [key, value] of Object.entries(revision.data)) {\n\t\t\t\tif (!key.startsWith(\"_\")) revisionData[key] = value;\n\t\t\t}\n\t\t\tconst mergedData = { ...liveData, ...revisionData };\n\t\t\t// Return a clone rather than mutating in place. The response\n\t\t\t// object isn't retained by the runtime today, but a future\n\t\t\t// request-cache layer would observe stale-after-mutation bugs;\n\t\t\t// cloning closes that footgun.\n\t\t\t// `r.data` was narrowed to `{ item?: ... }` at the top of this\n\t\t\t// method; spread its other keys (e.g. `_rev`) alongside the\n\t\t\t// hydrated item without going back through `unknown`.\n\t\t\treturn {\n\t\t\t\t...result,\n\t\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- shape preserved; result has been narrowed to the {success,data:{item}} envelope\n\t\t\t\tdata: {\n\t\t\t\t\t...r.data,\n\t\t\t\t\titem: { ...item, data: mergedData, liveData },\n\t\t\t\t},\n\t\t\t};\n\t\t} catch (error) {\n\t\t\t// Non-fatal — fall back to the unhydrated response. Log so the\n\t\t\t// failure isn't completely silent (the response will look stale\n\t\t\t// to the caller but no error is raised).\n\t\t\tconsole.error(\"[emdash] draft hydration failed:\", error);\n\t\t\treturn result;\n\t\t}\n\t}\n\n\tasync handleContentCreate(\n\t\tcollection: string,\n\t\tbody: {\n\t\t\tdata: Record<string, unknown>;\n\t\t\tslug?: string | null;\n\t\t\tstatus?: string;\n\t\t\tauthorId?: string;\n\t\t\tbylines?: Array<{ bylineId: string; roleLabel?: string | null }>;\n\t\t\tlocale?: string;\n\t\t\ttranslationOf?: string;\n\t\t\ttaxonomies?: Record<string, string[]>;\n\t\t},\n\t) {\n\t\ttry {\n\t\t\tconst git = await this.gitStoreFor(collection);\n\t\t\tif (git) {\n\t\t\t\tconst item = await git.create({ slug: body.slug, status: body.status, locale: body.locale, data: body.data });\n\t\t\t\treturn { success: true as const, data: { item, _rev: item.updatedAt } };\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn this.gitError(error, \"CONTENT_CREATE_ERROR\", \"Failed to create content\");\n\t\t}\n\t\t// Run beforeSave hooks (trusted plugins)\n\t\tlet processedData = body.data;\n\t\tif (this.hooks.hasHooks(\"content:beforeSave\")) {\n\t\t\tconst hookResult = await this.hooks.runContentBeforeSave(body.data, collection, true);\n\t\t\tprocessedData = hookResult.content;\n\t\t}\n\n\t\t// Run beforeSave hooks (sandboxed plugins)\n\t\tprocessedData = await this.runSandboxedBeforeSave(processedData, collection, true);\n\n\t\t// Normalize media fields (fill dimensions, storageKey, etc.)\n\t\tprocessedData = await this.normalizeMediaFields(collection, processedData);\n\n\t\t// Validate against the collection schema. Hook output is validated\n\t\t// rather than `body.data` so plugins that mutate field values can't\n\t\t// sneak invalid data past.\n\t\tconst { validateContentData } = await import(\"./api/handlers/validation.js\");\n\t\tconst validation = await validateContentData(this.db, collection, processedData, {\n\t\t\tpartial: false,\n\t\t});\n\t\tif (!validation.ok) {\n\t\t\treturn {\n\t\t\t\tsuccess: false as const,\n\t\t\t\terror: validation.error,\n\t\t\t};\n\t\t}\n\n\t\t// Create the content\n\t\tconst result = await handleContentCreate(this.db, collection, {\n\t\t\t...body,\n\t\t\tdata: processedData,\n\t\t\tauthorId: body.authorId,\n\t\t\tbylines: body.bylines,\n\t\t});\n\t\tif (result.success && result.data) {\n\t\t\tawait this.refreshContentUsageAfterSuccessfulWrite(collection, [result.data.item.id]);\n\t\t}\n\n\t\t// Run afterSave hooks (fire-and-forget)\n\t\tif (result.success && result.data) {\n\t\t\tthis.runAfterSaveHooks(contentItemToRecord(result.data.item), collection, true);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tasync handleContentUpdate(\n\t\tcollection: string,\n\t\tid: string,\n\t\tbody: {\n\t\t\tdata?: Record<string, unknown>;\n\t\t\tslug?: string | null;\n\t\t\tstatus?: string;\n\t\t\tauthorId?: string | null;\n\t\t\tbylines?: Array<{ bylineId: string; roleLabel?: string | null }>;\n\t\t\tseo?: {\n\t\t\t\ttitle?: string | null;\n\t\t\t\tdescription?: string | null;\n\t\t\t\timage?: string | null;\n\t\t\t\tcanonical?: string | null;\n\t\t\t\tnoIndex?: boolean;\n\t\t\t};\n\t\t\ttaxonomies?: Record<string, string[]>;\n\t\t\tpublishedAt?: string | null;\n\t\t\tlocale?: string;\n\t\t\t/** Replace the previous autosave revision after staging this save. */\n\t\t\tskipRevision?: boolean;\n\t\t\t_rev?: string;\n\t\t},\n\t) {\n\t\ttry {\n\t\t\tconst git = await this.gitStoreFor(collection);\n\t\t\tif (git) {\n\t\t\t\tconst item = await git.update(id, { slug: body.slug, status: body.status, data: body.data });\n\t\t\t\treturn { success: true as const, data: { item, _rev: item.updatedAt } };\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn this.gitError(error, \"CONTENT_UPDATE_ERROR\", \"Failed to update content\");\n\t\t}\n\t\t// Resolve slug → ID if needed (before any lookups)\n\t\tconst repo = new ContentRepository(this.db);\n\t\tconst resolvedItem = await repo.findByIdOrSlug(collection, id, body.locale);\n\t\tconst resolvedId = resolvedItem?.id ?? id;\n\n\t\t// Validate _rev early — before draft revision writes which modify updated_at.\n\t\t// After validation, strip _rev so the handler doesn't double-check against\n\t\t// the now-modified timestamp.\n\t\tif (body._rev) {\n\t\t\tif (!resolvedItem) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false as const,\n\t\t\t\t\terror: { code: \"NOT_FOUND\", message: `Content item not found: ${id}` },\n\t\t\t\t};\n\t\t\t}\n\t\t\tconst revCheck = validateRev(body._rev, resolvedItem);\n\t\t\tif (!revCheck.valid) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false as const,\n\t\t\t\t\terror: { code: \"CONFLICT\", message: revCheck.message },\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\t\tconst { _rev: _discardedRev, ...bodyWithoutRev } = body;\n\n\t\t// Run beforeSave hooks if data is provided\n\t\tlet processedData = bodyWithoutRev.data;\n\t\tif (bodyWithoutRev.data) {\n\t\t\tif (this.hooks.hasHooks(\"content:beforeSave\")) {\n\t\t\t\tconst hookResult = await this.hooks.runContentBeforeSave(\n\t\t\t\t\tbodyWithoutRev.data,\n\t\t\t\t\tcollection,\n\t\t\t\t\tfalse,\n\t\t\t\t);\n\t\t\t\tprocessedData = hookResult.content;\n\t\t\t}\n\n\t\t\t// Run sandboxed beforeSave hooks\n\t\t\tprocessedData = await this.runSandboxedBeforeSave(processedData!, collection, false);\n\n\t\t\t// Normalize media fields (fill dimensions, storageKey, etc.)\n\t\t\tprocessedData = await this.normalizeMediaFields(collection, processedData);\n\n\t\t\t// Validate field-level shape BEFORE the draft-revision write so\n\t\t\t// invalid updates can't silently land in revision history.\n\t\t\tconst { validateContentData } = await import(\"./api/handlers/validation.js\");\n\t\t\tconst validation = await validateContentData(this.db, collection, processedData, {\n\t\t\t\tpartial: true,\n\t\t\t});\n\t\t\tif (!validation.ok) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false as const,\n\t\t\t\t\terror: validation.error,\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\t// Draft-aware revision handling (if collection supports revisions)\n\t\t// Content table columns = published data (never written by saves).\n\t\t// Draft data lives only in the revisions table.\n\t\tlet usesDraftRevisions = false;\n\t\tlet draftStorageChanged = false;\n\t\tif (processedData) {\n\t\t\tconst collectionInfo = await this.schemaRegistry.getCollectionWithFields(collection);\n\t\t\tif (collectionInfo?.supports?.includes(\"revisions\")) {\n\t\t\t\tusesDraftRevisions = true;\n\t\t\t\tconst revisionRepo = new RevisionRepository(this.db);\n\t\t\t\tlet existing = await repo.findById(collection, resolvedId);\n\n\t\t\t\tfor (let attempt = 0; existing && attempt < MAX_DRAFT_STAGE_ATTEMPTS; attempt++) {\n\t\t\t\t\tlet baseData: Record<string, unknown>;\n\t\t\t\t\tif (existing.draftRevisionId) {\n\t\t\t\t\t\tconst draftRevision = await revisionRepo.findById(existing.draftRevisionId);\n\t\t\t\t\t\tbaseData = draftRevision?.data ?? existing.data;\n\t\t\t\t\t} else {\n\t\t\t\t\t\tbaseData = existing.data;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst mergedData = { ...baseData, ...processedData };\n\t\t\t\t\tif (bodyWithoutRev.slug !== undefined) {\n\t\t\t\t\t\tmergedData._slug = bodyWithoutRev.slug;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst revision = await revisionRepo.create({\n\t\t\t\t\t\tcollection,\n\t\t\t\t\t\tentryId: resolvedId,\n\t\t\t\t\t\tdata: mergedData,\n\t\t\t\t\t\tauthorId: bodyWithoutRev.authorId ?? undefined,\n\t\t\t\t\t});\n\n\t\t\t\t\tlet staged: boolean;\n\t\t\t\t\ttry {\n\t\t\t\t\t\tstaged = await repo.replaceDraftRevision(collection, resolvedId, revision.id, existing);\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait revisionRepo.deleteIfUnreferenced(collection, resolvedId, revision.id);\n\t\t\t\t\t\t} catch (cleanupError) {\n\t\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t\t`[emdash] Failed to clean up unstaged revision ${revision.id}:`,\n\t\t\t\t\t\t\t\tcleanupError,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tthrow error;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!staged) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait revisionRepo.deleteIfUnreferenced(collection, resolvedId, revision.id);\n\t\t\t\t\t\t} catch (cleanupError) {\n\t\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t\t`[emdash] Failed to clean up unstaged revision ${revision.id}:`,\n\t\t\t\t\t\t\t\tcleanupError,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (body._rev || attempt === MAX_DRAFT_STAGE_ATTEMPTS - 1) {\n\t\t\t\t\t\t\tconst error = new ContentMutationConflictError();\n\t\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\t\tsuccess: false as const,\n\t\t\t\t\t\t\t\terror: { code: \"CONFLICT\", message: error.message },\n\t\t\t\t\t\t\t};\n\t\t\t\t\t\t}\n\t\t\t\t\t\texisting = await repo.findById(collection, resolvedId);\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tdraftStorageChanged = true;\n\n\t\t\t\t\tif (bodyWithoutRev.skipRevision && existing.draftRevisionId) {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait revisionRepo.deleteIfUnreferenced(\n\t\t\t\t\t\t\t\tcollection,\n\t\t\t\t\t\t\t\tresolvedId,\n\t\t\t\t\t\t\t\texisting.draftRevisionId,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t\t`[emdash] Failed to clean up superseded revision ${existing.draftRevisionId}:`,\n\t\t\t\t\t\t\t\terror,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tafter(async () => {\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\tawait revisionRepo.pruneQueuedEntry(collection, resolvedId, revision.id, 50);\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t\t\t`[revisions] Failed to prune revisions for ${collection}/${resolvedId}:`,\n\t\t\t\t\t\t\t\t\terror,\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t// Public HTML comes from live columns / SEO / taxonomies, not draft revisions.\n\t\tconst liveMetaTouched = Object.entries(bodyWithoutRev).some(\n\t\t\t([key, value]) => value !== undefined && !DRAFT_ONLY_UPDATE_KEYS.has(key),\n\t\t);\n\n\t\t// Update the content table:\n\t\t// - If collection uses draft revisions: only update metadata (no data fields, no slug)\n\t\t// - Otherwise: update everything as before\n\t\tconst result =\n\t\t\tusesDraftRevisions && !liveMetaTouched\n\t\t\t\t? await handleContentGet(this.db, collection, resolvedId)\n\t\t\t\t: await handleContentUpdate(this.db, collection, resolvedId, {\n\t\t\t\t\t\t...bodyWithoutRev,\n\t\t\t\t\t\tdata: usesDraftRevisions ? undefined : processedData,\n\t\t\t\t\t\tslug: usesDraftRevisions ? undefined : bodyWithoutRev.slug,\n\t\t\t\t\t\tauthorId: bodyWithoutRev.authorId,\n\t\t\t\t\t\tbylines: bodyWithoutRev.bylines,\n\t\t\t\t\t});\n\n\t\tconst liveContentChanged = usesDraftRevisions\n\t\t\t? liveMetaTouched\n\t\t\t: Boolean(processedData || bodyWithoutRev.slug !== undefined || liveMetaTouched);\n\n\t\t// Hydrate draft data BEFORE firing afterSave hooks so the hook sees\n\t\t// the same effective data the response surfaces — for revision-\n\t\t// supporting collections, that's the just-saved draft, not the live\n\t\t// columns.\n\t\tconst hydrated = await this.hydrateDraftData(result);\n\t\tif (hydrated.success && hydrated.data) {\n\t\t\tconst contentIdsToRefresh = [resolvedId];\n\t\t\tif (!usesDraftRevisions && processedData) {\n\t\t\t\ttry {\n\t\t\t\t\tcontentIdsToRefresh.push(\n\t\t\t\t\t\t...(await findNonTranslatableSiblingContentIds(\n\t\t\t\t\t\t\tthis.db,\n\t\t\t\t\t\t\tcollection,\n\t\t\t\t\t\t\tresolvedId,\n\t\t\t\t\t\t\thydrated.data.item.translationGroup,\n\t\t\t\t\t\t\tprocessedData,\n\t\t\t\t\t\t)),\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`[media-usage] Failed to discover synced i18n siblings for ${collection}/${resolvedId}:`,\n\t\t\t\t\t\terror,\n\t\t\t\t\t);\n\t\t\t\t\ttry {\n\t\t\t\t\t\tawait markContentMediaUsageCollectionStale(\n\t\t\t\t\t\t\tthis.db,\n\t\t\t\t\t\t\tcollection,\n\t\t\t\t\t\t\t\"CONTENT_USAGE_REFRESH_ERROR\",\n\t\t\t\t\t\t);\n\t\t\t\t\t} catch (staleError) {\n\t\t\t\t\t\tconsole.error(`[media-usage] Failed to mark ${collection} stale:`, staleError);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\tawait this.refreshContentUsageAfterSuccessfulWrite(collection, contentIdsToRefresh);\n\t\t} else if (draftStorageChanged) {\n\t\t\ttry {\n\t\t\t\tawait markContentMediaUsageCollectionStale(this.db, collection, \"CONTENT_USAGE_STALE\");\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(`[media-usage] Failed to mark ${collection} stale:`, error);\n\t\t\t}\n\t\t}\n\n\t\t// Run afterSave hooks (fire-and-forget)\n\t\tif (hydrated.success && hydrated.data) {\n\t\t\tthis.runAfterSaveHooks(contentItemToRecord(hydrated.data.item), collection, false);\n\t\t}\n\n\t\tif (hydrated.success) {\n\t\t\treturn { ...hydrated, liveContentChanged };\n\t\t}\n\t\treturn hydrated;\n\t}\n\n\tasync handleContentDelete(collection: string, id: string) {\n\t\ttry {\n\t\t\tconst git = await this.gitStoreFor(collection);\n\t\t\tif (git) {\n\t\t\t\tconst removed = await git.remove(id);\n\t\t\t\tif (!removed)\n\t\t\t\t\treturn { success: false as const, error: { code: \"NOT_FOUND\", message: `Content item not found: ${id}` } };\n\t\t\t\treturn { success: true as const, data: { deleted: true } };\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn this.gitError(error, \"CONTENT_DELETE_ERROR\", \"Failed to delete content\");\n\t\t}\n\t\t// Run beforeDelete hooks (trusted plugins)\n\t\tif (this.hooks.hasHooks(\"content:beforeDelete\")) {\n\t\t\tconst { allowed } = await this.hooks.runContentBeforeDelete(id, collection);\n\t\t\tif (!allowed) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"DELETE_BLOCKED\",\n\t\t\t\t\t\tmessage: \"Delete blocked by plugin hook\",\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t}\n\n\t\t// Run sandboxed beforeDelete hooks\n\t\tconst sandboxAllowed = await this.runSandboxedBeforeDelete(id, collection);\n\t\tif (!sandboxAllowed) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"DELETE_BLOCKED\",\n\t\t\t\t\tmessage: \"Delete blocked by sandboxed plugin hook\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\t// Delete the content\n\t\tconst result = await handleContentDelete(this.db, collection, id);\n\t\tif (result.success) {\n\t\t\tawait this.refreshContentUsageAfterSuccessfulWrite(collection, [result.data.id]);\n\t\t}\n\n\t\t// Run afterDelete hooks (deferred past the response via after())\n\t\tif (result.success) {\n\t\t\tthis.runAfterDeleteHooks(id, collection, false);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\t// =========================================================================\n\t// Trash Handlers\n\t// =========================================================================\n\n\tasync handleContentListTrashed(\n\t\tcollection: string,\n\t\tparams: { cursor?: string; limit?: number } = {},\n\t) {\n\t\treturn handleContentListTrashed(this.db, collection, params);\n\t}\n\n\tasync handleContentRestore(collection: string, id: string) {\n\t\tconst result = await handleContentRestore(this.db, collection, id);\n\t\tif (result.success && result.data) {\n\t\t\tawait this.refreshContentUsageAfterSuccessfulWrite(collection, [result.data.item.id]);\n\t\t}\n\n\t\t// Run afterRestore hooks (fire-and-forget)\n\t\tif (result.success) {\n\t\t\tthis.runAfterRestoreHooks(contentItemToRecord(result.data.item), collection);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tasync handleContentPermanentDelete(collection: string, id: string) {\n\t\tconst result = await handleContentPermanentDelete(this.db, collection, id);\n\t\tif (result.success) {\n\t\t\tawait this.deleteContentUsageAfterSuccessfulPermanentDelete(collection, result.data.id);\n\t\t}\n\n\t\t// Run afterDelete hooks so plugins (e.g. AI Search) can clean up\n\t\tif (result.success) {\n\t\t\tthis.runAfterDeleteHooks(id, collection, true);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tasync handleContentCountTrashed(collection: string) {\n\t\treturn handleContentCountTrashed(this.db, collection);\n\t}\n\n\tasync handleContentDuplicate(collection: string, id: string, authorId?: string) {\n\t\tconst result = await handleContentDuplicate(this.db, collection, id, authorId);\n\t\tif (result.success && result.data) {\n\t\t\tawait this.refreshContentUsageAfterSuccessfulWrite(collection, [result.data.item.id]);\n\t\t}\n\t\treturn result;\n\t}\n\n\t// =========================================================================\n\t// Publishing & Scheduling Handlers\n\t// =========================================================================\n\n\tasync handleContentPublish(\n\t\tcollection: string,\n\t\tid: string,\n\t\toptions: {\n\t\t\tpublishedAt?: string;\n\t\t\trequireScheduledDue?: boolean;\n\t\t\texpectedScheduledAt?: string;\n\t\t} = {},\n\t) {\n\t\ttry {\n\t\t\tconst git = await this.gitStoreFor(collection);\n\t\t\tif (git) {\n\t\t\t\tconst item = await git.update(id, { status: \"published\" });\n\t\t\t\treturn { success: true as const, data: { item } };\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn this.gitError(error, \"CONTENT_PUBLISH_ERROR\", \"Failed to publish content\");\n\t\t}\n\t\tconst result = await handleContentPublish(this.db, collection, id, options);\n\t\tif (result.success && result.data) {\n\t\t\tawait this.refreshContentUsageAfterSuccessfulWrite(collection, [result.data.item.id]);\n\t\t}\n\n\t\t// Run afterPublish hooks (fire-and-forget)\n\t\tif (result.success && result.data) {\n\t\t\tthis.runAfterPublishHooks(contentItemToRecord(result.data.item), collection);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tasync handleContentUnpublish(collection: string, id: string) {\n\t\ttry {\n\t\t\tconst git = await this.gitStoreFor(collection);\n\t\t\tif (git) {\n\t\t\t\tconst item = await git.update(id, { status: \"draft\" });\n\t\t\t\treturn { success: true as const, data: { item } };\n\t\t\t}\n\t\t} catch (error) {\n\t\t\treturn this.gitError(error, \"CONTENT_UNPUBLISH_ERROR\", \"Failed to unpublish content\");\n\t\t}\n\t\tconst result = await handleContentUnpublish(this.db, collection, id);\n\t\tif (result.success && result.data) {\n\t\t\tawait this.refreshContentUsageAfterSuccessfulWrite(collection, [result.data.item.id]);\n\t\t}\n\n\t\t// Run afterUnpublish hooks (deferred past the response via after())\n\t\tif (result.success && result.data) {\n\t\t\tthis.runAfterUnpublishHooks(contentItemToRecord(result.data.item), collection);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tasync handleContentSchedule(collection: string, id: string, scheduledAt: string) {\n\t\tconst result = await handleContentSchedule(this.db, collection, id, scheduledAt);\n\t\tif (result.success && result.data) {\n\t\t\tawait this.refreshContentUsageAfterSuccessfulWrite(collection, [result.data.item.id]);\n\t\t}\n\n\t\t// Run afterSchedule hooks (fire-and-forget)\n\t\tif (result.success && result.data) {\n\t\t\tthis.runAfterScheduleHooks(contentItemToRecord(result.data.item), collection);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tasync handleContentUnschedule(collection: string, id: string) {\n\t\tconst result = await handleContentUnschedule(this.db, collection, id);\n\t\tif (result.success && result.data) {\n\t\t\tawait this.refreshContentUsageAfterSuccessfulWrite(collection, [result.data.item.id]);\n\t\t}\n\n\t\t// Run afterUnschedule hooks (fire-and-forget)\n\t\tif (result.success && result.data) {\n\t\t\tthis.runAfterUnscheduleHooks(contentItemToRecord(result.data.item), collection);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tasync handleContentCountScheduled(collection: string) {\n\t\treturn handleContentCountScheduled(this.db, collection);\n\t}\n\n\tasync handleContentDiscardDraft(collection: string, id: string) {\n\t\tconst result = await handleContentDiscardDraft(this.db, collection, id);\n\t\tif (result.success && result.data) {\n\t\t\tawait this.refreshContentUsageAfterSuccessfulWrite(collection, [result.data.item.id]);\n\t\t}\n\t\treturn result;\n\t}\n\n\tasync handleContentCompare(collection: string, id: string) {\n\t\treturn handleContentCompare(this.db, collection, id);\n\t}\n\n\tasync handleContentTranslations(collection: string, id: string) {\n\t\treturn handleContentTranslations(this.db, collection, id);\n\t}\n\n\t// =========================================================================\n\t// Media Handlers\n\t// =========================================================================\n\n\tasync handleMediaList(params: {\n\t\tcursor?: string;\n\t\tlimit?: number;\n\t\tmimeType?: string | readonly string[];\n\t\tq?: string;\n\t}) {\n\t\treturn handleMediaList(this.db, params);\n\t}\n\n\tasync handleMediaGet(id: string) {\n\t\treturn handleMediaGet(this.db, id);\n\t}\n\n\tasync handleMediaCreate(input: {\n\t\tfilename: string;\n\t\tmimeType: string;\n\t\tsize?: number;\n\t\twidth?: number;\n\t\theight?: number;\n\t\tstorageKey: string;\n\t\tcontentHash?: string;\n\t\tblurhash?: string;\n\t\tdominantColor?: string;\n\t\tauthorId?: string;\n\t}) {\n\t\t// Run beforeUpload hooks\n\t\tlet processedInput = input;\n\t\tif (this.hooks.hasHooks(\"media:beforeUpload\")) {\n\t\t\tconst hookResult = await this.hooks.runMediaBeforeUpload({\n\t\t\t\tname: input.filename,\n\t\t\t\ttype: input.mimeType,\n\t\t\t\tsize: input.size || 0,\n\t\t\t});\n\t\t\tprocessedInput = {\n\t\t\t\t...input,\n\t\t\t\tfilename: hookResult.file.name,\n\t\t\t\tmimeType: hookResult.file.type,\n\t\t\t\tsize: hookResult.file.size,\n\t\t\t};\n\t\t}\n\n\t\t// Create the media record\n\t\tconst result = await handleMediaCreate(this.db, processedInput);\n\n\t\t// Run afterUpload hooks (fire-and-forget)\n\t\tif (result.success && this.hooks.hasHooks(\"media:afterUpload\")) {\n\t\t\tconst item = result.data.item;\n\t\t\tconst mediaItem: MediaItem = {\n\t\t\t\tid: item.id,\n\t\t\t\tfilename: item.filename,\n\t\t\t\tmimeType: item.mimeType,\n\t\t\t\tsize: item.size,\n\t\t\t\turl: `/media/${item.id}/${item.filename}`,\n\t\t\t\tcreatedAt: item.createdAt,\n\t\t\t};\n\t\t\tthis.hooks\n\t\t\t\t.runMediaAfterUpload(mediaItem)\n\t\t\t\t.catch((err) => console.error(\"EmDash afterUpload hook error:\", err));\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tasync handleMediaUpdate(\n\t\tid: string,\n\t\tinput: { alt?: string; caption?: string; width?: number; height?: number },\n\t) {\n\t\tconst result = await handleMediaUpdate(this.db, id, input);\n\t\t// Resolved media references in site settings (`logo`, `favicon`,\n\t\t// `seo.defaultOgImage`) bake in the media row's `contentType`,\n\t\t// `width`, and `height`. A metadata edit invalidates that snapshot\n\t\t// for every entry point: REST routes, MCP tools, plugin code, and\n\t\t// any future caller of `handleMediaUpdate`. Cross-isolate staleness\n\t\t// remains bounded by isolate lifetime.\n\t\tif (result.success) {\n\t\t\tinvalidateSiteSettingsCache();\n\t\t}\n\t\treturn result;\n\t}\n\n\tasync handleMediaDelete(id: string) {\n\t\tconst result = await handleMediaDelete(this.db, id);\n\t\t// Same reasoning as `handleMediaUpdate`: if the deleted media row\n\t\t// was referenced by a setting, the cached resolved URL now points\n\t\t// at a 404. Invalidation is unconditional on success — cheaper than\n\t\t// querying which settings reference the id.\n\t\tif (result.success) {\n\t\t\tinvalidateSiteSettingsCache();\n\t\t}\n\t\treturn result;\n\t}\n\n\t// =========================================================================\n\t// Revision Handlers\n\t// =========================================================================\n\n\tasync handleRevisionList(collection: string, entryId: string, params: { limit?: number } = {}) {\n\t\treturn handleRevisionList(this.db, collection, entryId, params);\n\t}\n\n\tasync handleRevisionGet(revisionId: string) {\n\t\treturn handleRevisionGet(this.db, revisionId);\n\t}\n\n\tasync handleRevisionRestore(revisionId: string, callerUserId: string) {\n\t\t// Discover the parent entry up front so we can branch on whether\n\t\t// the collection uses draft revisions.\n\t\tconst revisionRepo = new RevisionRepository(this.db);\n\t\tconst revision = await revisionRepo.findById(revisionId);\n\t\tif (!revision) {\n\t\t\treturn {\n\t\t\t\tsuccess: false as const,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\tmessage: `Revision not found: ${revisionId}`,\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\n\t\tconst collectionInfo = await this.schemaRegistry.getCollectionWithFields(revision.collection);\n\t\tconst usesDraftRevisions = collectionInfo?.supports?.includes(\"revisions\") ?? false;\n\n\t\t// Non-revision collections: keep the legacy behavior of writing the\n\t\t// revision's data straight onto the live row. This preserves\n\t\t// behavior for collections that opt out of the draft model.\n\t\tif (!usesDraftRevisions) {\n\t\t\tconst result = await handleRevisionRestore(this.db, revisionId, callerUserId);\n\t\t\tif (result.success) {\n\t\t\t\tawait this.refreshContentUsageAfterSuccessfulWrite(revision.collection, [revision.entryId]);\n\t\t\t}\n\t\t\treturn this.hydrateDraftData(result);\n\t\t}\n\n\t\t// Revision-capable collections: restore is \"make this revision the\n\t\t// current draft\". The live row's data columns are left untouched\n\t\t// (only `draft_revision_id` changes — no `updated_at` stamp, since\n\t\t// restoring to draft is the same kind of draft-only staging as\n\t\t// Save/Autosave and must not register a phantom modification for\n\t\t// sitemap <lastmod> / JSON-LD dateModified, #2143). The caller\n\t\t// must then `content_publish` to promote the restored draft to\n\t\t// live, matching the documented tool contract.\n\t\ttry {\n\t\t\tconst contentRepo = new ContentRepository(this.db);\n\t\t\tconst existing = await contentRepo.findById(revision.collection, revision.entryId);\n\t\t\tif (!existing) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false as const,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: \"NOT_FOUND\",\n\t\t\t\t\t\tmessage: `Content item not found: ${revision.entryId}`,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\n\t\t\tconst newDraft = await revisionRepo.create({\n\t\t\t\tcollection: revision.collection,\n\t\t\t\tentryId: revision.entryId,\n\t\t\t\tdata: revision.data,\n\t\t\t\tauthorId: callerUserId,\n\t\t\t});\n\n\t\t\ttry {\n\t\t\t\tconst staged = await contentRepo.replaceDraftRevision(\n\t\t\t\t\trevision.collection,\n\t\t\t\t\trevision.entryId,\n\t\t\t\t\tnewDraft.id,\n\t\t\t\t\texisting,\n\t\t\t\t);\n\t\t\t\tif (!staged) throw new ContentMutationConflictError();\n\t\t\t} catch (error) {\n\t\t\t\ttry {\n\t\t\t\t\tawait revisionRepo.deleteIfUnreferenced(\n\t\t\t\t\t\trevision.collection,\n\t\t\t\t\t\trevision.entryId,\n\t\t\t\t\t\tnewDraft.id,\n\t\t\t\t\t);\n\t\t\t\t} catch (cleanupError) {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`[emdash] Failed to clean up unrestored revision ${newDraft.id}:`,\n\t\t\t\t\t\tcleanupError,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t\tthrow error;\n\t\t\t}\n\n\t\t\tafter(async () => {\n\t\t\t\ttry {\n\t\t\t\t\tawait revisionRepo.pruneQueuedEntry(\n\t\t\t\t\t\trevision.collection,\n\t\t\t\t\t\trevision.entryId,\n\t\t\t\t\t\tnewDraft.id,\n\t\t\t\t\t\t50,\n\t\t\t\t\t);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t`[revisions] Failed to prune revisions for ${revision.collection}/${revision.entryId}:`,\n\t\t\t\t\t\terror,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t});\n\n\t\t\t// Return the freshly-fetched item with the new draft hydrated\n\t\t\t// onto `data`. Without this the response would echo the live\n\t\t\t// columns and the next `content_get` would surface different\n\t\t\t// values (the bug that motivated this rewrite).\n\t\t\tconst refetched = await handleContentGet(this.db, revision.collection, revision.entryId);\n\t\t\tconst hydrated = await this.hydrateDraftData(refetched);\n\t\t\tif (hydrated.success) {\n\t\t\t\tawait this.refreshContentUsageAfterSuccessfulWrite(revision.collection, [revision.entryId]);\n\t\t\t}\n\t\t\treturn hydrated;\n\t\t} catch (error) {\n\t\t\tif (error instanceof ContentMutationConflictError) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false as const,\n\t\t\t\t\terror: { code: \"CONFLICT\", message: error.message },\n\t\t\t\t};\n\t\t\t}\n\t\t\tconsole.error(\"[emdash] revision restore failed:\", error);\n\t\t\treturn {\n\t\t\t\tsuccess: false as const,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"REVISION_RESTORE_ERROR\",\n\t\t\t\t\tmessage: \"Failed to restore revision\",\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t}\n\n\tprivate async refreshContentUsageAfterSuccessfulWrite(\n\t\tcollection: string,\n\t\tcontentIds: readonly string[],\n\t): Promise<void> {\n\t\tfor (const contentId of new Set(contentIds)) {\n\t\t\ttry {\n\t\t\t\tconst work = await processMediaUsageWorkAfterWrite(this.db, collection, contentId);\n\t\t\t\tif (work.outcome !== \"inactive\") return;\n\t\t\t\tawait refreshContentMediaUsageAfterWrite(this.db, collection, contentId);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[media-usage] Failed after content write ${collection}/${contentId}:`,\n\t\t\t\t\terror,\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async deleteContentUsageAfterSuccessfulPermanentDelete(\n\t\tcollection: string,\n\t\tcontentId: string,\n\t): Promise<void> {\n\t\ttry {\n\t\t\tconst work = await processMediaUsageWorkAfterWrite(this.db, collection, contentId);\n\t\t\tif (work.outcome !== \"inactive\") return;\n\t\t\tconst result = await deleteContentMediaUsage(this.db, collection, contentId);\n\t\t\tif (!result.success) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t`[media-usage] Usage delete for ${collection}/${contentId} finished with ${result.errorCode}`,\n\t\t\t\t);\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tconsole.error(\n\t\t\t\t`[media-usage] Failed after permanent content delete ${collection}/${contentId}:`,\n\t\t\t\terror,\n\t\t\t);\n\t\t}\n\t}\n\n\t// =========================================================================\n\t// Plugin Routes\n\t// =========================================================================\n\n\t/**\n\t * Get route metadata for a plugin route without invoking the handler.\n\t * Used by the catch-all route to decide auth before dispatch.\n\t * Returns null if the plugin or route doesn't exist.\n\t */\n\t/**\n\t * Every route enabled plugins expose under /_emdash/api/plugins/<id>/…,\n\t * with the access each one requires — the plugin half of the route\n\t * catalogue policies are written against.\n\t */\n\tlistPluginRoutes(): Array<{ pluginId: string; route: string; public: boolean; permission: string }> {\n\t\tconst out: Array<{ pluginId: string; route: string; public: boolean; permission: string }> = [];\n\t\tconst seen = new Set<string>();\n\t\tconst push = (pluginId: string, route: string, meta: RouteMeta) => {\n\t\t\tconst key = `${pluginId}/${route}`;\n\t\t\tif (seen.has(key)) return;\n\t\t\tseen.add(key);\n\t\t\tout.push({\n\t\t\t\tpluginId,\n\t\t\t\troute,\n\t\t\t\tpublic: meta.public,\n\t\t\t\tpermission: meta.permission ?? \"plugins:manage\",\n\t\t\t});\n\t\t};\n\t\tfor (const plugin of this.configuredPlugins) {\n\t\t\tif (!this.isPluginEnabled(plugin.id)) continue;\n\t\t\tfor (const [name, route] of Object.entries(plugin.routes ?? {})) push(plugin.id, name, buildRouteMeta(route));\n\t\t}\n\t\tfor (const [pluginId, routes] of sandboxedRouteMetaCache) {\n\t\t\tif (!this.isPluginEnabled(pluginId)) continue;\n\t\t\tfor (const [name, meta] of routes) push(pluginId, name, meta);\n\t\t}\n\t\treturn out.toSorted((a, b) => a.pluginId.localeCompare(b.pluginId) || a.route.localeCompare(b.route));\n\t}\n\n\tgetPluginRouteMeta(pluginId: string, path: string): RouteMeta | null {\n\t\tif (!this.isPluginEnabled(pluginId)) return null;\n\n\t\tconst routeKey = path.replace(LEADING_SLASH_PATTERN, \"\");\n\n\t\t// Check trusted plugins first\n\t\tconst trustedPlugin = this.configuredPlugins.find((p) => p.id === pluginId);\n\t\tif (trustedPlugin) {\n\t\t\tconst route = trustedPlugin.routes[routeKey];\n\t\t\tif (!route) return null;\n\t\t\treturn buildRouteMeta(route);\n\t\t}\n\n\t\t// Check sandboxed plugin route metadata cache\n\t\tconst meta = sandboxedRouteMetaCache.get(pluginId);\n\t\tif (meta) {\n\t\t\tconst routeMeta = meta.get(routeKey);\n\t\t\tif (routeMeta) return routeMeta;\n\t\t}\n\n\t\t// The \"admin\" route is implicitly available for any sandboxed plugin\n\t\t// that declares admin pages or widgets. This handles plugins installed\n\t\t// from bundles that predate the explicit admin route requirement.\n\t\tif (routeKey === \"admin\") {\n\t\t\tconst manifestMeta = marketplaceManifestCache.get(pluginId);\n\t\t\tif (manifestMeta?.admin?.pages?.length || manifestMeta?.admin?.widgets?.length) {\n\t\t\t\treturn { public: false };\n\t\t\t}\n\t\t\t// Also check build-time sandboxed entries\n\t\t\tconst entry = this.sandboxedPluginEntries.find((e) => e.id === pluginId);\n\t\t\tif (entry?.adminPages?.length || entry?.adminWidgets?.length) {\n\t\t\t\treturn { public: false };\n\t\t\t}\n\t\t}\n\n\t\t// Fallback: if the plugin exists in the sandbox cache, allow the route.\n\t\t// The sandbox runner will return an error if the route doesn't actually exist.\n\t\tif (this.findSandboxedPlugin(pluginId)) {\n\t\t\treturn { public: false };\n\t\t}\n\n\t\treturn null;\n\t}\n\n\t/**\n\t * Resolve the settings schema for a runtime-installed (marketplace or\n\t * registry) plugin from its cached manifest. Returns `{}` for a known\n\t * plugin without a schema and `null` for unknown plugins, matching the\n\t * contract of `getPluginSettingsSchema` for build-time plugins.\n\t */\n\tgetRuntimePluginSettingsSchema(pluginId: string): Record<string, SettingField> | null {\n\t\tconst meta = marketplaceManifestCache.get(pluginId);\n\t\tif (!meta) return null;\n\t\treturn meta.admin?.settingsSchema ?? {};\n\t}\n\n\tasync handlePluginApiRoute(\n\t\tpluginId: string,\n\t\t_method: string,\n\t\tpath: string,\n\t\trequest: Request,\n\t\tuser?: RouteCallerInput | null,\n\t) {\n\t\tif (!this.isPluginEnabled(pluginId)) {\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: { code: \"NOT_FOUND\", message: `Plugin not enabled: ${pluginId}` },\n\t\t\t};\n\t\t}\n\n\t\t// Authenticated caller for `ctx.user`. Undefined for public routes\n\t\t// (the catch-all only forwards the caller after private-route auth)\n\t\t// and for machine tokens with no bound user.\n\t\tconst caller = user ? toRouteCallerInfo(user) : undefined;\n\n\t\t// Check trusted (configured) plugins first — this must match the\n\t\t// resolution order in getPluginRouteMeta to avoid auth/execution mismatches.\n\t\tconst trustedPlugin = this.configuredPlugins.find((p) => p.id === pluginId);\n\t\tif (trustedPlugin && this.enabledPlugins.has(trustedPlugin.id)) {\n\t\t\tconst routeRegistry = new PluginRouteRegistry({\n\t\t\t\t...this.pipelineFactoryOptions,\n\t\t\t\temailPipeline: this.email ?? undefined,\n\t\t\t\tcronReschedule: () => this.cronScheduler?.reschedule(),\n\t\t\t\ttrustedProxyHeaders: getTrustedProxyHeaders(this.config),\n\t\t\t});\n\t\t\trouteRegistry.register(trustedPlugin);\n\n\t\t\tconst routeKey = path.replace(LEADING_SLASH_PATTERN, \"\");\n\n\t\t\t// Body methods parse JSON; GET/HEAD/DELETE parse the query string (#2146).\n\t\t\tconst body = await parseRouteInput(request);\n\n\t\t\treturn routeRegistry.invoke(pluginId, routeKey, { request, body, user: caller });\n\t\t}\n\n\t\t// Check sandboxed (marketplace) plugins second\n\t\tconst sandboxedPlugin = this.findSandboxedPlugin(pluginId);\n\t\tif (sandboxedPlugin) {\n\t\t\treturn this.handleSandboxedRoute(sandboxedPlugin, path, request, caller);\n\t\t}\n\n\t\treturn {\n\t\t\tsuccess: false,\n\t\t\terror: { code: \"NOT_FOUND\", message: `Plugin not found: ${pluginId}` },\n\t\t};\n\t}\n\n\tasync getPluginMcpTools(pluginId?: string) {\n\t\tconst tools: Array<{\n\t\t\tpluginId: string;\n\t\t\tname: string;\n\t\t\tdescription: string;\n\t\t\troute: string;\n\t\t\tpermission: string;\n\t\t\tdestructive: boolean;\n\t\t\tinputSchema: z.ZodType;\n\t\t\toutputSchema?: z.ZodType;\n\t\t}> = [];\n\t\tconst seen = new Set<string>();\n\n\t\tfor (const plugin of this.configuredPlugins) {\n\t\t\tif (pluginId && plugin.id !== pluginId) continue;\n\t\t\tfor (const [name, tool] of Object.entries(plugin.mcp?.tools ?? {})) {\n\t\t\t\tconst route = plugin.routes[tool.route];\n\t\t\t\tif (!route || route.public || !route.permission || !(route.permission in Permissions))\n\t\t\t\t\tcontinue;\n\t\t\t\tconst key = `${plugin.id}__${name}`;\n\t\t\t\tif (seen.has(key)) continue;\n\t\t\t\tseen.add(key);\n\t\t\t\ttools.push({\n\t\t\t\t\tpluginId: plugin.id,\n\t\t\t\t\tname,\n\t\t\t\t\tdescription: tool.description,\n\t\t\t\t\troute: tool.route,\n\t\t\t\t\tpermission: route.permission,\n\t\t\t\t\tdestructive: tool.destructive ?? false,\n\t\t\t\t\tinputSchema: tool.input,\n\t\t\t\t\toutputSchema: tool.output,\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\n\t\tconst addManifestTools = (id: string, mcp: PluginMcpManifestConfig | undefined) => {\n\t\t\tif (pluginId && id !== pluginId) return;\n\t\t\tfor (const tool of mcp?.tools ?? []) {\n\t\t\t\tconst key = `${id}__${tool.name}`;\n\t\t\t\tconst routeMeta = this.getPluginRouteMeta(id, tool.route);\n\t\t\t\tif (\n\t\t\t\t\tseen.has(key) ||\n\t\t\t\t\t!routeMeta ||\n\t\t\t\t\trouteMeta.public ||\n\t\t\t\t\trouteMeta.permission !== tool.permission ||\n\t\t\t\t\t!(tool.permission in Permissions)\n\t\t\t\t) {\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tseen.add(key);\n\t\t\t\ttools.push({\n\t\t\t\t\tpluginId: id,\n\t\t\t\t\tname: tool.name,\n\t\t\t\t\tdescription: tool.description,\n\t\t\t\t\troute: tool.route,\n\t\t\t\t\tpermission: tool.permission,\n\t\t\t\t\tdestructive: tool.destructive,\n\t\t\t\t\tinputSchema: z.fromJSONSchema({ ...tool.inputSchema }),\n\t\t\t\t\toutputSchema: tool.outputSchema ? z.fromJSONSchema({ ...tool.outputSchema }) : undefined,\n\t\t\t\t});\n\t\t\t}\n\t\t};\n\n\t\tfor (const entry of this.sandboxedPluginEntries) addManifestTools(entry.id, entry.mcp);\n\t\tfor (const [id, manifest] of marketplaceManifestCache) addManifestTools(id, manifest.mcp);\n\n\t\treturn tools;\n\t}\n\n\tasync getEnabledPluginMcpTools() {\n\t\tconst [tools, states] = await Promise.all([\n\t\t\tthis.getPluginMcpTools(),\n\t\t\tnew PluginStateRepository(this.db).getAll(),\n\t\t]);\n\t\tconst stateByPlugin = new Map(states.map((state) => [state.pluginId, state]));\n\t\treturn tools.filter((tool) => {\n\t\t\tconst state = stateByPlugin.get(tool.pluginId);\n\t\t\tif (\n\t\t\t\t!state?.mcpToolsEnabled ||\n\t\t\t\tstate.status !== \"active\" ||\n\t\t\t\t!this.isPluginEnabled(tool.pluginId)\n\t\t\t) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t\tconst consented = state.mcpToolsConsent;\n\t\t\treturn consented === this.serializePluginMcpConsent(tools, tool.pluginId);\n\t\t});\n\t}\n\n\tserializePluginMcpConsent(\n\t\ttools: Awaited<ReturnType<EmDashRuntime[\"getPluginMcpTools\"]>>,\n\t\tpluginId: string,\n\t): string {\n\t\treturn JSON.stringify(\n\t\t\ttools\n\t\t\t\t.filter((tool) => tool.pluginId === pluginId)\n\t\t\t\t.map((tool) => ({\n\t\t\t\t\tname: tool.name,\n\t\t\t\t\tdescription: tool.description,\n\t\t\t\t\troute: tool.route,\n\t\t\t\t\tpermission: tool.permission,\n\t\t\t\t\tdestructive: tool.destructive,\n\t\t\t\t\tinputSchema: z.toJSONSchema(tool.inputSchema, { target: \"draft-7\" }),\n\t\t\t\t\t...(tool.outputSchema\n\t\t\t\t\t\t? { outputSchema: z.toJSONSchema(tool.outputSchema, { target: \"draft-7\" }) }\n\t\t\t\t\t\t: {}),\n\t\t\t\t}))\n\t\t\t\t.toSorted((a, b) => a.name.localeCompare(b.name)),\n\t\t);\n\t}\n\n\tasync handlePluginMcpTool(\n\t\tpluginId: string,\n\t\ttoolName: string,\n\t\troute: string,\n\t\tinput: unknown,\n\t\tactorId: string,\n\t\trequest: Request,\n\t\tcaller?: RouteCallerInput | null,\n\t) {\n\t\tconst requestMeta = extractRequestMeta(request, getTrustedProxyHeaders(this.config));\n\t\tconst audit = new AuditRepository(this.db);\n\t\tconst headers = new Headers(request.headers);\n\t\theaders.delete(\"content-length\");\n\t\theaders.delete(\"content-encoding\");\n\t\tconst internalRequest = new Request(request.url, {\n\t\t\tmethod: \"POST\",\n\t\t\theaders,\n\t\t\tbody: JSON.stringify(input),\n\t\t});\n\t\tconst result = await this.handlePluginApiRoute(\n\t\t\tpluginId,\n\t\t\t\"POST\",\n\t\t\troute,\n\t\t\tinternalRequest,\n\t\t\tcaller,\n\t\t);\n\t\tawait audit.log({\n\t\t\tactorId,\n\t\t\tactorIp: requestMeta.ip ?? undefined,\n\t\t\taction: \"plugin_tool_invoke\",\n\t\t\tresourceType: \"plugin_mcp_tool\",\n\t\t\tresourceId: `${pluginId}__${toolName}`,\n\t\t\tdetails: { pluginId, tool: toolName, route },\n\t\t\tstatus: result.success ? \"success\" : \"failure\",\n\t\t});\n\t\treturn result;\n\t}\n\n\tasync handlePluginMcpDenied(\n\t\tpluginId: string,\n\t\ttoolName: string,\n\t\troute: string,\n\t\tactorId: string,\n\t\trequest: Request,\n\t\treason: string,\n\t): Promise<void> {\n\t\tconst requestMeta = extractRequestMeta(request, getTrustedProxyHeaders(this.config));\n\t\tawait new AuditRepository(this.db).log({\n\t\t\tactorId,\n\t\t\tactorIp: requestMeta.ip ?? undefined,\n\t\t\taction: \"plugin_tool_invoke\",\n\t\t\tresourceType: \"plugin_mcp_tool\",\n\t\t\tresourceId: `${pluginId}__${toolName}`,\n\t\t\tdetails: { pluginId, tool: toolName, route, reason },\n\t\t\tstatus: \"denied\",\n\t\t});\n\t}\n\n\t// =========================================================================\n\t// Sandboxed Plugin Helpers\n\t// =========================================================================\n\n\tprivate findSandboxedPlugin(pluginId: string): SandboxedPluginInstance | undefined {\n\t\tfor (const [key, plugin] of this.sandboxedPlugins) {\n\t\t\tif (key.startsWith(pluginId + \":\")) {\n\t\t\t\treturn plugin;\n\t\t\t}\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Normalize image/file fields in content data.\n\t * Fills missing dimensions, storageKey, mimeType, and filename from providers.\n\t */\n\tprivate async normalizeMediaFields(\n\t\tcollection: string,\n\t\tdata: Record<string, unknown>,\n\t): Promise<Record<string, unknown>> {\n\t\tlet collectionInfo;\n\t\ttry {\n\t\t\tcollectionInfo = await this.schemaRegistry.getCollectionWithFields(collection);\n\t\t} catch {\n\t\t\treturn data;\n\t\t}\n\t\tif (!collectionInfo?.fields) return data;\n\n\t\tconst imageFields = collectionInfo.fields.filter(\n\t\t\t(f) => f.type === \"image\" || f.type === \"file\",\n\t\t);\n\t\t// Repeater fields can contain image sub-fields, whose values need the same normalization\n\t\t// (a bare media id posted inside a repeater item would otherwise be stored verbatim and\n\t\t// render as \"Image not found\" in the admin).\n\t\tconst repeaterFields = collectionInfo.fields.filter(\n\t\t\t(f) => f.type === \"repeater\" && Array.isArray(f.validation?.subFields),\n\t\t);\n\t\tif (imageFields.length === 0 && repeaterFields.length === 0) return data;\n\n\t\tconst getProvider = (id: string) => this.getMediaProvider(id);\n\t\tconst result = { ...data };\n\n\t\tfor (const field of imageFields) {\n\t\t\tconst value = result[field.slug];\n\t\t\tif (value == null) continue;\n\n\t\t\ttry {\n\t\t\t\tconst normalized = await normalizeMediaValue(value, getProvider);\n\t\t\t\tif (normalized) {\n\t\t\t\t\tresult[field.slug] = normalized;\n\t\t\t\t}\n\t\t\t} catch {\n\t\t\t\t// Don't fail the save if normalization fails for a single field\n\t\t\t}\n\t\t}\n\n\t\tfor (const field of repeaterFields) {\n\t\t\tconst value = result[field.slug];\n\t\t\tif (!Array.isArray(value)) continue;\n\n\t\t\tconst mediaSubFieldSlugs = (field.validation?.subFields ?? [])\n\t\t\t\t.filter((sub) => sub.type === \"image\")\n\t\t\t\t.map((sub) => sub.slug);\n\t\t\tif (mediaSubFieldSlugs.length === 0) continue;\n\n\t\t\tconst items: unknown[] = value;\n\t\t\tresult[field.slug] = await Promise.all(\n\t\t\t\titems.map(async (item) => {\n\t\t\t\t\tif (!isRecord(item)) return item;\n\t\t\t\t\tconst normalizedItem: Record<string, unknown> = { ...item };\n\t\t\t\t\tfor (const slug of mediaSubFieldSlugs) {\n\t\t\t\t\t\tconst subValue = normalizedItem[slug];\n\t\t\t\t\t\tif (subValue == null) continue;\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tconst normalized = await normalizeMediaValue(subValue, getProvider);\n\t\t\t\t\t\t\tif (normalized) {\n\t\t\t\t\t\t\t\tnormalizedItem[slug] = normalized;\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch {\n\t\t\t\t\t\t\t// Don't fail the save if normalization fails for a single sub-field\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\treturn normalizedItem;\n\t\t\t\t}),\n\t\t\t);\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tprivate async runSandboxedBeforeSave(\n\t\tcontent: Record<string, unknown>,\n\t\tcollection: string,\n\t\tisNew: boolean,\n\t): Promise<Record<string, unknown>> {\n\t\tlet result = content;\n\n\t\tfor (const [pluginKey, plugin] of this.sandboxedPlugins) {\n\t\t\tconst [id] = pluginKey.split(\":\");\n\t\t\tif (!id || !this.isPluginEnabled(id)) continue;\n\n\t\t\ttry {\n\t\t\t\tconst hookResult = await plugin.invokeHook(\"content:beforeSave\", {\n\t\t\t\t\tcontent: result,\n\t\t\t\t\tcollection,\n\t\t\t\t\tisNew,\n\t\t\t\t});\n\t\t\t\tif (hookResult && typeof hookResult === \"object\" && !Array.isArray(hookResult)) {\n\t\t\t\t\t// Sandbox returns unknown; convert to record by iterating own properties\n\t\t\t\t\tconst record: Record<string, unknown> = {};\n\t\t\t\t\tfor (const [k, v] of Object.entries(hookResult)) {\n\t\t\t\t\t\trecord[k] = v;\n\t\t\t\t\t}\n\t\t\t\t\tresult = record;\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(`EmDash: Sandboxed plugin ${id} beforeSave hook error:`, error);\n\t\t\t}\n\t\t}\n\n\t\treturn result;\n\t}\n\n\tprivate async runSandboxedBeforeDelete(id: string, collection: string): Promise<boolean> {\n\t\tfor (const [pluginKey, plugin] of this.sandboxedPlugins) {\n\t\t\tconst [pluginId] = pluginKey.split(\":\");\n\t\t\tif (!pluginId || !this.isPluginEnabled(pluginId)) continue;\n\n\t\t\ttry {\n\t\t\t\tconst result = await plugin.invokeHook(\"content:beforeDelete\", {\n\t\t\t\t\tid,\n\t\t\t\t\tcollection,\n\t\t\t\t});\n\t\t\t\tif (result === false) {\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(`EmDash: Sandboxed plugin ${pluginId} beforeDelete hook error:`, error);\n\t\t\t}\n\t\t}\n\n\t\treturn true;\n\t}\n\n\tprivate runAfterSaveHooks(\n\t\tcontent: Record<string, unknown>,\n\t\tcollection: string,\n\t\tisNew: boolean,\n\t): void {\n\t\tafter(async () => {\n\t\t\t// Trusted plugins\n\t\t\tif (this.hooks.hasHooks(\"content:afterSave\")) {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.hooks.runContentAfterSave(content, collection, isNew);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconsole.error(\"EmDash afterSave hook error:\", err);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Sandboxed plugins\n\t\t\tconst tasks: Promise<void>[] = [];\n\t\t\tfor (const [pluginKey, plugin] of this.sandboxedPlugins) {\n\t\t\t\tconst [id] = pluginKey.split(\":\");\n\t\t\t\tif (!id || !this.isPluginEnabled(id)) continue;\n\n\t\t\t\ttasks.push(\n\t\t\t\t\t(async () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait plugin.invokeHook(\"content:afterSave\", { content, collection, isNew });\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconsole.error(`EmDash: Sandboxed plugin ${id} afterSave error:`, err);\n\t\t\t\t\t\t}\n\t\t\t\t\t})(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait Promise.allSettled(tasks);\n\t\t});\n\t}\n\n\tprivate runAfterDeleteHooks(id: string, collection: string, permanent: boolean): void {\n\t\tafter(async () => {\n\t\t\t// Trusted plugins\n\t\t\tif (this.hooks.hasHooks(\"content:afterDelete\")) {\n\t\t\t\ttry {\n\t\t\t\t\tawait this.hooks.runContentAfterDelete(id, collection, permanent);\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconsole.error(\"EmDash afterDelete hook error:\", err);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Sandboxed plugins\n\t\t\tconst tasks: Promise<void>[] = [];\n\t\t\tfor (const [pluginKey, plugin] of this.sandboxedPlugins) {\n\t\t\t\tconst [pluginId] = pluginKey.split(\":\");\n\t\t\t\tif (!pluginId || !this.isPluginEnabled(pluginId)) continue;\n\n\t\t\t\ttasks.push(\n\t\t\t\t\t(async () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait plugin.invokeHook(\"content:afterDelete\", { id, collection, permanent });\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconsole.error(`EmDash: Sandboxed plugin ${pluginId} afterDelete error:`, err);\n\t\t\t\t\t\t}\n\t\t\t\t\t})(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait Promise.allSettled(tasks);\n\t\t});\n\t}\n\n\tprivate runDeferredContentHook(\n\t\tname:\n\t\t\t| \"content:afterPublish\"\n\t\t\t| \"content:afterUnpublish\"\n\t\t\t| \"content:afterRestore\"\n\t\t\t| \"content:afterSchedule\"\n\t\t\t| \"content:afterUnschedule\",\n\t\tcontent: Record<string, unknown>,\n\t\tcollection: string,\n\t): void {\n\t\tconst label = name.slice(\"content:\".length);\n\n\t\tafter(async () => {\n\t\t\t// Trusted plugins\n\t\t\tif (this.hooks.hasHooks(name)) {\n\t\t\t\ttry {\n\t\t\t\t\tswitch (name) {\n\t\t\t\t\t\tcase \"content:afterPublish\":\n\t\t\t\t\t\t\tawait this.hooks.runContentAfterPublish(content, collection);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"content:afterUnpublish\":\n\t\t\t\t\t\t\tawait this.hooks.runContentAfterUnpublish(content, collection);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"content:afterRestore\":\n\t\t\t\t\t\t\tawait this.hooks.runContentAfterRestore(content, collection);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"content:afterSchedule\":\n\t\t\t\t\t\t\tawait this.hooks.runContentAfterSchedule(content, collection);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\tcase \"content:afterUnschedule\":\n\t\t\t\t\t\t\tawait this.hooks.runContentAfterUnschedule(content, collection);\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t} catch (err) {\n\t\t\t\t\tconsole.error(`EmDash ${label} hook error:`, err);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\t// Sandboxed plugins\n\t\t\tconst tasks: Promise<void>[] = [];\n\t\t\tfor (const [pluginKey, plugin] of this.sandboxedPlugins) {\n\t\t\t\tconst [pluginId] = pluginKey.split(\":\");\n\t\t\t\tif (!pluginId || !this.isPluginEnabled(pluginId)) continue;\n\n\t\t\t\ttasks.push(\n\t\t\t\t\t(async () => {\n\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\tawait plugin.invokeHook(name, { content, collection });\n\t\t\t\t\t\t} catch (err) {\n\t\t\t\t\t\t\tconsole.error(`EmDash: Sandboxed plugin ${pluginId} ${label} error:`, err);\n\t\t\t\t\t\t}\n\t\t\t\t\t})(),\n\t\t\t\t);\n\t\t\t}\n\t\t\tawait Promise.allSettled(tasks);\n\t\t});\n\t}\n\n\tprivate runAfterPublishHooks(content: Record<string, unknown>, collection: string): void {\n\t\tthis.runDeferredContentHook(\"content:afterPublish\", content, collection);\n\t}\n\n\tprivate runAfterUnpublishHooks(content: Record<string, unknown>, collection: string): void {\n\t\tthis.runDeferredContentHook(\"content:afterUnpublish\", content, collection);\n\t}\n\n\tprivate runAfterRestoreHooks(content: Record<string, unknown>, collection: string): void {\n\t\tthis.runDeferredContentHook(\"content:afterRestore\", content, collection);\n\t}\n\n\tprivate runAfterScheduleHooks(content: Record<string, unknown>, collection: string): void {\n\t\tthis.runDeferredContentHook(\"content:afterSchedule\", content, collection);\n\t}\n\n\tprivate runAfterUnscheduleHooks(content: Record<string, unknown>, collection: string): void {\n\t\tthis.runDeferredContentHook(\"content:afterUnschedule\", content, collection);\n\t}\n\n\tprivate async handleSandboxedRoute(\n\t\tplugin: SandboxedPluginInstance,\n\t\tpath: string,\n\t\trequest: Request,\n\t\tuser?: UserInfo,\n\t): Promise<{\n\t\tsuccess: boolean;\n\t\tdata?: unknown;\n\t\terror?: { code: string; message: string };\n\t\tstatus?: number;\n\t}> {\n\t\tconst routeName = path.replace(LEADING_SLASH_PATTERN, \"\");\n\n\t\t// Body methods parse JSON; GET/HEAD/DELETE parse the query string (#2146).\n\t\tconst body = await parseRouteInput(request);\n\n\t\ttry {\n\t\t\tconst headers = sanitizeHeadersForSandbox(request.headers);\n\t\t\tconst meta = extractRequestMeta(request, this.config);\n\t\t\tconst result = await plugin.invokeRoute(routeName, body, {\n\t\t\t\turl: request.url,\n\t\t\t\tmethod: request.method,\n\t\t\t\theaders,\n\t\t\t\tmeta,\n\t\t\t\tuser,\n\t\t\t});\n\t\t\treturn { success: true, data: result };\n\t\t} catch (error) {\n\t\t\tconsole.error(`EmDash: Sandboxed plugin route error:`, error);\n\t\t\tconst sandboxRouteError = getSandboxRouteErrorDetails(error);\n\t\t\tif (sandboxRouteError) {\n\t\t\t\treturn {\n\t\t\t\t\tsuccess: false,\n\t\t\t\t\tstatus: sandboxRouteError.status,\n\t\t\t\t\terror: {\n\t\t\t\t\t\tcode: sandboxRouteError.code,\n\t\t\t\t\t\tmessage: sandboxRouteError.message,\n\t\t\t\t\t},\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tsuccess: false,\n\t\t\t\terror: {\n\t\t\t\t\tcode: \"ROUTE_ERROR\",\n\t\t\t\t\tmessage: error instanceof Error ? error.message : String(error),\n\t\t\t\t},\n\t\t\t};\n\t\t}\n\t}\n\n\t// =========================================================================\n\t// Public Page Contributions\n\t// =========================================================================\n\n\t/**\n\t * Cache for page contributions. Uses a WeakMap keyed on the PublicPageContext\n\t * object so results are collected once per page context per request, even when\n\t * multiple render components (EmDashHead, EmDashBodyStart, EmDashBodyEnd)\n\t * request contributions from the same page.\n\t */\n\tprivate pageContributionCache = new WeakMap<PublicPageContext, Promise<PageContributions>>();\n\n\t/**\n\t * Collect all page contributions (metadata + fragments) in a single pass.\n\t * Results are cached by page context object identity.\n\t */\n\tasync collectPageContributions(page: PublicPageContext): Promise<PageContributions> {\n\t\tconst cached = this.pageContributionCache.get(page);\n\t\tif (cached) return cached;\n\n\t\tconst promise = this.doCollectPageContributions(page);\n\t\tthis.pageContributionCache.set(page, promise);\n\t\treturn promise;\n\t}\n\n\tprivate async doCollectPageContributions(page: PublicPageContext): Promise<PageContributions> {\n\t\tconst metadata: PageMetadataContribution[] = [];\n\t\tconst fragments: PageFragmentContribution[] = [];\n\n\t\t// Trusted plugins via HookPipeline — both metadata and fragments\n\t\tif (this.hooks.hasHooks(\"page:metadata\")) {\n\t\t\tconst results = await this.hooks.runPageMetadata({ page });\n\t\t\tfor (const r of results) {\n\t\t\t\tmetadata.push(...r.contributions);\n\t\t\t}\n\t\t}\n\n\t\tif (this.hooks.hasHooks(\"page:fragments\")) {\n\t\t\tconst results = await this.hooks.runPageFragments({ page });\n\t\t\tfor (const r of results) {\n\t\t\t\tfragments.push(...r.contributions);\n\t\t\t}\n\t\t}\n\n\t\t// Sandboxed plugins — metadata only, never fragments\n\t\tfor (const [pluginKey, plugin] of this.sandboxedPlugins) {\n\t\t\tconst [id] = pluginKey.split(\":\");\n\t\t\tif (!id || !this.isPluginEnabled(id)) continue;\n\n\t\t\ttry {\n\t\t\t\tconst result = await plugin.invokeHook(\"page:metadata\", { page });\n\t\t\t\tif (result != null) {\n\t\t\t\t\tconst items = Array.isArray(result) ? result : [result];\n\t\t\t\t\tfor (const item of items) {\n\t\t\t\t\t\tif (isValidMetadataContribution(item)) {\n\t\t\t\t\t\t\tmetadata.push(item);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error(`EmDash: Sandboxed plugin ${id} page:metadata error:`, error);\n\t\t\t}\n\t\t}\n\n\t\treturn { metadata, fragments };\n\t}\n\n\t/**\n\t * Collect page metadata contributions from trusted and sandboxed plugins.\n\t * Delegates to the single-pass collector and returns the metadata portion.\n\t */\n\tasync collectPageMetadata(page: PublicPageContext): Promise<PageMetadataContribution[]> {\n\t\tconst { metadata } = await this.collectPageContributions(page);\n\t\treturn metadata;\n\t}\n\n\t/**\n\t * Collect page fragment contributions from trusted plugins only.\n\t * Delegates to the single-pass collector and returns the fragments portion.\n\t */\n\tasync collectPageFragments(page: PublicPageContext): Promise<PageFragmentContribution[]> {\n\t\tconst { fragments } = await this.collectPageContributions(page);\n\t\treturn fragments;\n\t}\n\n\tprivate isPluginEnabled(pluginId: string): boolean {\n\t\tconst status = this.pluginStates.get(pluginId);\n\t\treturn status === undefined || status === \"active\";\n\t}\n}\n","/**\n * Public media URL resolution.\n *\n * Used at render time by the Image components to decide whether a storage\n * key should be served from the configured `publicUrl` (R2 custom domain,\n * S3 CDN) or through the internal `/_emdash/api/media/file/{key}` route.\n */\nimport type { Storage } from \"../storage/types.js\";\nimport { INTERNAL_MEDIA_PREFIX } from \"./normalize.js\";\n\n// Keys accepted by the public-URL rewrite: the `{ulid}{ext}` shape produced by\n// the upload pipeline, with letters, digits, dots, dashes, and underscores.\n// Slashes, `?`, `#`, and `%` are rejected so attacker-controlled content in a\n// portable-text `asset.url` cannot traverse or reroute on the CDN origin.\nconst SAFE_STORAGE_KEY = /^[A-Za-z0-9._-]+$/;\n\n/**\n * Resolve the public URL for a locally stored media key. Returns an empty\n * string when no key is given. When a storage adapter is supplied, defers to\n * `storage.getPublicUrl()`; otherwise returns the internal proxy route.\n */\nexport function resolvePublicMediaUrl(\n\tstorage: Storage | null | undefined,\n\tstorageKey: string,\n): string {\n\tif (!storageKey) return \"\";\n\tif (storage) return storage.getPublicUrl(storageKey);\n\treturn `/_emdash/api/media/file/${storageKey}`;\n}\n\n/**\n * Build the `getPublicMediaUrl` closure attached to `Astro.locals.emdash`.\n * Shared by the anonymous fast path and the full-runtime path in middleware.\n *\n * @internal\n */\nexport function createPublicMediaUrlResolver(\n\tstorage: Storage | null | undefined,\n): (key: string) => string {\n\treturn (key) => resolvePublicMediaUrl(storage, key);\n}\n\n/** Input shape for {@link buildRenderMediaUrl}. */\nexport interface RenderMediaRef {\n\t/** Storage key with extension (the canonical shape from the upload pipeline). */\n\tstorageKey?: string;\n\t/** Pre-baked URL (either an internal proxy URL or an external URL). */\n\turl?: string;\n\t/** Bare media id (ULID without extension); only the internal proxy can look this up. */\n\tid?: string;\n}\n\n/**\n * Build a render-time media URL. Prefers `storageKey`, then rewrites an\n * internal `url` via `resolve`, then falls back to the internal proxy for a\n * bare `id`. External URLs and non-matching internal-looking URLs pass\n * through untouched. Returns `\"\"` when nothing usable is present.\n *\n * @internal\n */\nexport function buildRenderMediaUrl(\n\tresolve: ((key: string) => string) | undefined,\n\tref: RenderMediaRef,\n): string {\n\tconst { storageKey, url, id } = ref;\n\tif (storageKey) {\n\t\treturn resolve ? resolve(storageKey) : `${INTERNAL_MEDIA_PREFIX}${storageKey}`;\n\t}\n\tif (url) {\n\t\tif (resolve && url.startsWith(INTERNAL_MEDIA_PREFIX)) {\n\t\t\tconst key = url.slice(INTERNAL_MEDIA_PREFIX.length);\n\t\t\tif (SAFE_STORAGE_KEY.test(key)) return resolve(key);\n\t\t}\n\t\treturn url;\n\t}\n\tif (id) return `${INTERNAL_MEDIA_PREFIX}${id}`;\n\treturn \"\";\n}\n","/**\n * Request-scoped database lifecycle helpers.\n *\n * Extracted from middleware.ts so they can be unit-tested without pulling in\n * the virtual:emdash/* module graph. The middleware imports these to settle a\n * request-scoped db adapter's lifecycle around the response.\n */\n\nimport { createDeferredTaskTracker } from \"../../deferred-tasks.js\";\nimport type { DeferredTaskTracker } from \"../../deferred-tasks.js\";\n\n/**\n * Astro attaches AstroCookies to outgoing responses via a well-known global\n * symbol. Cloning a Response (`new Response(body, init)`) drops non-header\n * metadata, so any helper that wraps the response must explicitly forward this\n * symbol or `cookies.set()` calls will be silently dropped. `Symbol.for`\n * returns the same registry symbol everywhere, so this matches the copy in\n * middleware.ts.\n */\nexport const ASTRO_COOKIES_SYMBOL = Symbol.for(\"astro.cookies\");\n\ninterface ScopedDbLifecycle {\n\tcommit: () => void;\n\tclose?: () => void;\n}\n\n/**\n * Whether the request ended authenticated, for the db adapter's `commit()`.\n * `isAuthenticated` is captured before the route runs, so it misses\n * login/signup/invite requests that create the Astro session mid-request;\n * `session.set()` writes the `astro-session` cookie into the jar synchronously,\n * so scanning the outgoing cookies detects them. Only outgoing cookies count —\n * a stale `astro-session` cookie arriving on an anonymous render must not\n * qualify, or its replica-read bookmark would overwrite a fresher one from an\n * earlier authenticated write.\n */\nexport function requestEndedAuthenticated(\n\tisAuthenticated: boolean,\n\tcookies: { headers(): Iterable<string> },\n): boolean {\n\tif (isAuthenticated) return true;\n\tfor (const header of cookies.headers()) {\n\t\tif (header.startsWith(\"astro-session=\")) return true;\n\t}\n\treturn false;\n}\n\n/** Hold the real adapter close behind both response and deferred-task completion. */\nexport function coordinateScopedDbLifecycle(scoped: ScopedDbLifecycle): {\n\tclosed?: Promise<void>;\n\tdeferredTasks?: DeferredTaskTracker;\n\tlifecycle: ScopedDbLifecycle;\n} {\n\tif (!scoped.close) return { lifecycle: scoped };\n\n\tconst close = scoped.close;\n\tconst deferredTasks = createDeferredTaskTracker(() => {\n\t\ttry {\n\t\t\tclose();\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[emdash] request-scoped db close failed:\", error);\n\t\t}\n\t});\n\treturn {\n\t\tclosed: deferredTasks.settled,\n\t\tdeferredTasks,\n\t\tlifecycle: { commit: scoped.commit, close: deferredTasks.settle },\n\t};\n}\n\n/**\n * Run a request-scoped db's `close()` once the response body has finished\n * streaming. Astro streams HTML and components issue DB queries during that\n * stream, so a connection-backed adapter (e.g. Postgres over Hyperdrive) must\n * not be torn down until the body is flushed. Bodyless responses (redirects,\n * 304s, errors) close immediately. A guard makes close idempotent and a stream\n * `cancel` (client disconnect) still triggers it so connections never leak.\n *\n * No-op for adapters without a `close` (D1): the response passes through.\n */\nexport function wrapResponseForScopedClose(response: Response, close: () => void): Response {\n\tlet closed = false;\n\tconst runClose = () => {\n\t\tif (closed) return;\n\t\tclosed = true;\n\t\ttry {\n\t\t\tclose();\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[emdash] request-scoped db close failed:\", error);\n\t\t}\n\t};\n\n\tif (!response.body) {\n\t\trunClose();\n\t\treturn response;\n\t}\n\n\tconst transform = new TransformStream<Uint8Array, Uint8Array>({\n\t\tflush: runClose,\n\t\tcancel: runClose,\n\t});\n\tconst wrapped = new Response(response.body.pipeThrough(transform), response);\n\tconst astroCookies = Reflect.get(response, ASTRO_COOKIES_SYMBOL);\n\tif (astroCookies !== undefined) {\n\t\tReflect.set(wrapped, ASTRO_COOKIES_SYMBOL, astroCookies);\n\t}\n\t// Byte counts are preserved by the identity transform, but a stale\n\t// Content-Length on a reconstructed streaming Response risks truncation.\n\twrapped.headers.delete(\"Content-Length\");\n\treturn wrapped;\n}\n\n/**\n * Run the request body under a request-scoped db, then settle its lifecycle:\n * `commit()` runs before the response is returned (so per-request state like a\n * D1 bookmark cookie is persisted in the headers, even if render throws), while\n * `close()` (if any) is deferred to lifecycle settlement so a\n * connection-backed adapter isn't torn down mid-render or mid-task. On error\n * the lifecycle is settled before rethrowing so it never leaks.\n *\n * On the error path both `commit()` and `close()` are defended: a throw from\n * either is logged and swallowed so it can't replace the propagating render\n * error (which is the one the caller needs to see). On the success path\n * `commit()` is guarded too — if it throws, the connection is closed before the\n * failure is surfaced, so it never leaks. For the current adapters `commit()`\n * is a no-op (Hyperdrive) or a cookie write (D1, no `close`) and `close()` is\n * fire-and-forget, so these guards only matter for a future connection-backed\n * adapter with throwing teardown, but the helper is generic and must not leak\n * or mask.\n */\nexport async function finishScoped(\n\tscoped: ScopedDbLifecycle,\n\trun: () => Promise<Response>,\n): Promise<Response> {\n\tlet response: Response;\n\ttry {\n\t\tresponse = await run();\n\t} catch (error) {\n\t\t// A render error is already propagating; neither commit nor close may\n\t\t// mask it, and close must still run so the connection doesn't leak.\n\t\tcommitSafely(scoped.commit);\n\t\tcloseSafely(scoped.close);\n\t\tthrow error;\n\t}\n\ttry {\n\t\tscoped.commit();\n\t} catch (error) {\n\t\t// commit() failed on the success path: close the connection now (the\n\t\t// response won't be wrapped, so stream-end close would never run) and\n\t\t// surface the failure. close is swallowed so it can't mask the commit\n\t\t// error that the caller needs to see.\n\t\tcloseSafely(scoped.close);\n\t\tthrow error;\n\t}\n\treturn scoped.close ? wrapResponseForScopedClose(response, scoped.close) : response;\n}\n\n/**\n * Run commit() swallowing any error. Used where an exception is already\n * propagating (or about to be thrown) and a commit failure must neither mask it\n * nor skip the subsequent close().\n */\nfunction commitSafely(commit: () => void): void {\n\ttry {\n\t\tcommit();\n\t} catch (error) {\n\t\tconsole.error(\"[emdash] request-scoped db commit failed during error handling:\", error);\n\t}\n}\n\n/**\n * Run close() swallowing any error. Used on the error/commit-failure paths\n * where another exception is the one the caller must see; a throwing teardown\n * must not replace it.\n */\nfunction closeSafely(close: (() => void) | undefined): void {\n\tif (!close) return;\n\ttry {\n\t\tclose();\n\t} catch (error) {\n\t\tconsole.error(\"[emdash] request-scoped db close failed during error handling:\", error);\n\t}\n}\n","/**\n * Stream-end metrics\n *\n * Server-Timing db.* counters are snapshotted when middleware's next()\n * returns — but at that point only the response *headers* are final.\n * Astro streams the body afterwards, and components rendered during\n * streaming issue further DB queries that the headers can never report.\n *\n * This module wraps the response body in an identity TransformStream and\n * snapshots the request metrics in flush(), i.e. when the body actually\n * finishes streaming. The metrics object lives on the request context\n * (AsyncLocalStorage) and is mutated in-place by the Kysely log hook, so\n * a reference captured before wrapping observes every post-header query.\n * The snapshot is emitted as prefixed NDJSON on stdout (same transport as\n * [emdash-query-log] — console.log works in both Node and workerd).\n *\n * Gated on isInstrumentationEnabled() (EMDASH_QUERY_LOG=1): zero overhead\n * in normal production traffic.\n */\n\nimport { flushRecorder, isInstrumentationEnabled } from \"../../database/instrumentation.js\";\nimport { getRequestContext } from \"../../request-context.js\";\n// Reuse the single source of truth for Astro's well-known cookies symbol\n// rather than redefining `Symbol.for(\"astro.cookies\")` here — it must stay in\n// lockstep with the copy the rest of the middleware forwards.\nimport { ASTRO_COOKIES_SYMBOL } from \"./scoped-db.js\";\n\nexport const STREAM_END_PREFIX = \"[emdash-stream-end]\";\n\n/** Shape of the NDJSON snapshot emitted when the body finishes streaming. */\nexport interface StreamEndSnapshot {\n\troute?: string;\n\tmethod?: string;\n\tphase?: string;\n\t/** Total elapsed ms from middleware entry to end of body streaming. */\n\ttotalMs: number;\n\tdbCount: number;\n\tdbTotalMs: number;\n\tdbFirstOffset: number | null;\n\tdbLastOffset: number | null;\n\tcacheHits: number;\n\tcacheMisses: number;\n}\n\n/**\n * Wrap a response body so the FINAL request metrics are emitted when the\n * body finishes streaming. Returns the response unchanged when\n * instrumentation is disabled, the body is null, or no request metrics\n * are attached (e.g. outside the middleware's ALS context).\n */\nexport function wrapBodyForStreamMetrics(response: Response): Response {\n\tif (!isInstrumentationEnabled()) return response;\n\tif (!response.body) return response;\n\n\t// Capture the context's metrics object BEFORE wrapping: flush() runs\n\t// after the middleware's ALS frame may have exited, but the object\n\t// reference stays live and is mutated in-place by the Kysely log hook.\n\tconst ctx = getRequestContext();\n\tconst metrics = ctx?.metrics;\n\tif (!metrics) return response;\n\tconst recorder = ctx?.queryRecorder;\n\n\t// Claim the per-query flush: the recorder is mutated in-place by the\n\t// Kysely log hook for the whole request, including queries issued by\n\t// components while the body streams. Flushing here (rather than when\n\t// middleware returns) is what captures those streaming queries. The\n\t// flag tells the middleware's fallback flush to leave this recorder\n\t// to us.\n\tif (recorder) recorder.deferredFlush = true;\n\n\tconst transform = new TransformStream<Uint8Array, Uint8Array>({\n\t\tflush() {\n\t\t\tconst snapshot: StreamEndSnapshot = {\n\t\t\t\troute: recorder?.route,\n\t\t\t\tmethod: recorder?.method,\n\t\t\t\tphase: recorder?.phase,\n\t\t\t\ttotalMs: performance.now() - metrics.start,\n\t\t\t\tdbCount: metrics.dbCount,\n\t\t\t\tdbTotalMs: metrics.dbTotalMs,\n\t\t\t\tdbFirstOffset: metrics.dbFirstOffset,\n\t\t\t\tdbLastOffset: metrics.dbLastOffset,\n\t\t\t\tcacheHits: metrics.cacheHits,\n\t\t\t\tcacheMisses: metrics.cacheMisses,\n\t\t\t};\n\t\t\tconsole.log(`${STREAM_END_PREFIX} ${JSON.stringify(snapshot)}`);\n\t\t\t// Emit the full per-query log now that streaming is complete.\n\t\t\tif (recorder) flushRecorder(recorder);\n\t\t},\n\t});\n\n\tconst wrapped = new Response(response.body.pipeThrough(transform), response);\n\tconst astroCookies = Reflect.get(response, ASTRO_COOKIES_SYMBOL);\n\tif (astroCookies !== undefined) {\n\t\tReflect.set(wrapped, ASTRO_COOKIES_SYMBOL, astroCookies);\n\t}\n\t// The identity transform preserves byte counts today, but a stale\n\t// Content-Length on a re-constructed streaming Response risks\n\t// truncation if anything upstream changes; the header is recomputed\n\t// (or chunked encoding used) by the server layer anyway.\n\twrapped.headers.delete(\"Content-Length\");\n\treturn wrapped;\n}\n","/**\n * Eager, transparent prefetch of site-global \"chrome\" data.\n *\n * On a public page render, the shared layout pulls the same site-global data on\n * every request -- menus, widget areas, taxonomy term lists, site settings --\n * but each is awaited inside a separately-rendered Astro component, so they\n * execute as serial DB round trips. This fires them all CONCURRENTLY at the\n * very start of the request, before `next()`:\n *\n *   - On remote backends (D1, Durable Objects) the round trips overlap instead\n *     of serializing, collapsing ~N sequential RTTs into ~1 wall-clock RTT. On\n *     a coalescing backend they additionally batch into a single round trip.\n *   - The results land in the per-request `requestCached` store under the exact\n *     keys the layout helpers use, so when the components render they hit a warm\n *     (in-flight or resolved) cache entry instead of issuing their own query.\n *\n * Nothing here changes what templates call -- it warms the real helpers, so the\n * cache keys and value shapes are guaranteed identical. The caller gates this to\n * the public-page path on a request-scoped (remote) backend; it is a no-op-ish\n * waste on synchronous local SQLite, so don't call it there.\n *\n * Fire-and-forget: never awaited by middleware, never throws (a prefetch failure\n * must not affect the request -- the helpers will simply run on demand).\n */\n\nimport { getDb } from \"../loader.js\";\nimport { getMenu } from \"../menus/index.js\";\nimport { setRequestCacheEntry } from \"../request-cache.js\";\nimport { getSiteSettings } from \"../settings/index.js\";\nimport { getTaxonomyDefs, getTaxonomyTerms } from \"../taxonomies/index.js\";\nimport { getWidgetAreas } from \"../widgets/index.js\";\n\n/** Warm widget areas: one bulk load, primed under each per-area cache key. */\nasync function prefetchWidgetAreas(): Promise<void> {\n\tconst areas = await getWidgetAreas();\n\t// getWidgetArea(name) caches under `widget-area:${name}` and returns the same\n\t// WidgetArea shape getWidgetAreas yields, so priming here makes those calls hit.\n\tfor (const area of areas) {\n\t\tsetRequestCacheEntry(`widget-area:${area.name}`, area);\n\t}\n}\n\n/**\n * Warm every taxonomy's term list via the real helper (primes per-name keys).\n * Counts are left out: they cost an aggregate over the whole assignment pivot\n * per taxonomy, and only a consumer that renders one can say it's needed. A\n * consumer that does asks for it and reuses the term list warmed here.\n */\nasync function prefetchTaxonomyTerms(): Promise<void> {\n\tconst defs = await getTaxonomyDefs();\n\tawait Promise.allSettled(defs.map((def) => getTaxonomyTerms(def.name, { includeCounts: false })));\n}\n\n/** Warm every menu via the real helper (primes `menu:${name}:${locale}`). */\nasync function prefetchMenus(): Promise<void> {\n\tconst db = await getDb();\n\t// The layout calls getMenu(name) with hardcoded names; we can't know them, so\n\t// discover every menu name and warm them all (small, bounded chrome table).\n\tconst rows = await db.selectFrom(\"_emdash_menus\").select(\"name\").distinct().execute();\n\tconst names = [...new Set(rows.map((r) => r.name))];\n\tawait Promise.allSettled(names.map((name) => getMenu(name)));\n}\n\n/**\n * Concurrently warm the site-global layout data for the current request.\n * Safe to call only inside the request ALS frame that owns the (remote)\n * request-scoped db. Never throws.\n */\nexport async function prefetchLayoutData(): Promise<void> {\n\ttry {\n\t\tawait Promise.allSettled([\n\t\t\tgetSiteSettings(),\n\t\t\tprefetchMenus(),\n\t\t\tprefetchWidgetAreas(),\n\t\t\tprefetchTaxonomyTerms(),\n\t\t]);\n\t} catch (error) {\n\t\t// Defensive: Promise.allSettled shouldn't reject, but never let a prefetch\n\t\t// failure surface to the request.\n\t\tconsole.error(\"[emdash] layout prefetch failed (non-fatal):\", error);\n\t}\n}\n","import type { RouteMeta } from \"../plugins/routes.js\";\nimport type { HandlerResponse } from \"./types.js\";\n\nexport type PublicPluginApiRouteHandler = (\n\tpluginId: string,\n\tmethod: string,\n\tpath: string,\n\trequest: Request,\n) => Promise<HandlerResponse>;\n\ninterface PublicPluginApiRouteRuntime {\n\tgetPluginRouteMeta(pluginId: string, path: string): RouteMeta | null;\n\thandlePluginApiRoute(\n\t\tpluginId: string,\n\t\tmethod: string,\n\t\tpath: string,\n\t\trequest: Request,\n\t): Promise<HandlerResponse>;\n}\n\nfunction pluginRouteNotFound(): HandlerResponse {\n\treturn {\n\t\tsuccess: false,\n\t\terror: {\n\t\t\tcode: \"NOT_FOUND\",\n\t\t\tmessage: \"Plugin route not found\",\n\t\t},\n\t};\n}\n\nexport function createPublicPluginApiRouteHandler(\n\truntime: PublicPluginApiRouteRuntime,\n): PublicPluginApiRouteHandler {\n\treturn async (pluginId, method, path, request) => {\n\t\tconst meta = runtime.getPluginRouteMeta(pluginId, path);\n\t\tif (meta?.public !== true) {\n\t\t\treturn pluginRouteNotFound();\n\t\t}\n\n\t\treturn runtime.handlePluginApiRoute(pluginId, method, path, request);\n\t};\n}\n","/**\n * EmDash middleware\n *\n * Thin wrapper that initializes EmDashRuntime and attaches it to locals.\n * All heavy lifting happens in EmDashRuntime.\n */\n\nimport type { APIContext } from \"astro\";\nimport { defineMiddleware } from \"astro:middleware\";\nimport type { Kysely } from \"kysely\";\n// Import from virtual modules (populated by integration at build time)\n// @ts-ignore - virtual module\nimport { buildTime as virtualBuildTime } from \"virtual:emdash/build\";\n// @ts-ignore - virtual module\nimport virtualConfig from \"virtual:emdash/config\";\n// @ts-ignore - virtual module\nimport {\n\tcreateCoalescingDialect as virtualCreateCoalescingDialect,\n\tcreateDialect as virtualCreateDialect,\n\tcreateRequestScopedDb as virtualCreateRequestScopedDb,\n} from \"virtual:emdash/dialect\";\nimport type { RequestScopedDbOpts } from \"virtual:emdash/dialect\";\n// @ts-ignore - virtual module\nimport { mediaProviders as virtualMediaProviders } from \"virtual:emdash/media-providers\";\n// @ts-ignore - virtual module\nimport { plugins as virtualPlugins } from \"virtual:emdash/plugins\";\n// @ts-ignore - virtual module\nimport * as virtualSandboxRunnerModule from \"virtual:emdash/sandbox-runner\";\n// @ts-ignore - virtual module\nimport { sandboxedPlugins as virtualSandboxedPlugins } from \"virtual:emdash/sandboxed-plugins\";\n// @ts-ignore - virtual module\nimport { createScheduler as virtualCreateScheduler } from \"virtual:emdash/scheduler\";\n// @ts-ignore - virtual module\nimport { createStorage as virtualCreateStorage } from \"virtual:emdash/storage\";\n\nimport { after } from \"../after.js\";\nimport {\n\tcreateRecorder,\n\tflushRecorder,\n\tisInstrumentationEnabled,\n} from \"../database/instrumentation.js\";\nimport {\n\tPendingMigrationsError,\n\tresolveRuntimeMigrationMode,\n\ttype RuntimeMigrationMode,\n} from \"../database/migrations/policy.js\";\nimport { createDeferredTaskTracker } from \"../deferred-tasks.js\";\nimport {\n\tDB_INIT_DEADLINE_MS,\n\tEmDashRuntime,\n\ttype MediaUsageMaintenanceResult,\n\ttype RuntimeDependencies,\n\ttype SandboxedPluginEntry,\n\ttype MediaProviderEntry,\n\ttype CreateSchedulerFn,\n} from \"../emdash-runtime.js\";\nimport { setI18nConfig } from \"../i18n/config.js\";\nimport type { Database, Storage } from \"../index.js\";\nimport { createPublicMediaUrlResolver } from \"../media/url.js\";\nimport { getLastContentWriteAt } from \"../object-cache/index.js\";\nimport type { SandboxRunnerFactory } from \"../plugins/sandbox/types.js\";\nimport type { ResolvedPlugin } from \"../plugins/types.js\";\nimport { invalidateUrlPatternCache } from \"../query.js\";\nimport {\n\tcreateRequestMetrics,\n\tgetRequestContext,\n\ttype RequestMetrics,\n\trunWithContext,\n} from \"../request-context.js\";\nimport type { PublishedRef } from \"../scheduled-publish.js\";\nimport { isMissingTableError } from \"../utils/db-errors.js\";\nimport { createInitLock, type InitLock, initWithLock } from \"../utils/init-lock.js\";\nimport type { EmDashConfig } from \"./integration/runtime.js\";\nimport {\n\tASTRO_COOKIES_SYMBOL,\n\tcoordinateScopedDbLifecycle,\n\tfinishScoped,\n\trequestEndedAuthenticated,\n} from \"./middleware/scoped-db.js\";\nimport { wrapBodyForStreamMetrics } from \"./middleware/stream-end-metrics.js\";\nimport { prefetchLayoutData } from \"./prefetch.js\";\nimport { createPublicPluginApiRouteHandler } from \"./public-plugin-api-routes.js\";\nimport { resolveSessionUser } from \"./session-user.js\";\nimport type { EmDashHandlers } from \"./types.js\";\n\n// Public type for withEmDashRuntime() consumers (queue/scheduled handlers).\nexport type { EmDashRuntime } from \"../emdash-runtime.js\";\n\n/**\n * Runtime init lock reclaim deadline. Must be strictly larger than the db\n * init deadline: this lock wraps EmDashRuntime.create() → getDatabase() →\n * the db init lock, and equal deadlines would let this outer lock reclaim\n * (spawning a second cron scheduler and sandbox runner) while the inner db\n * init is legitimately still working through a contended migration.\n */\nconst RUNTIME_INIT_DEADLINE_MS = DB_INIT_DEADLINE_MS + 15_000;\n\n/**\n * Throttle for the anonymous-path runtime-init failure log. While a site is\n * stuck (e.g. a failing migration in its backoff window, #1744) every\n * anonymous request lands in that catch; one line per interval per isolate\n * keeps the failure visible without flooding logs on a busy site. Plain\n * module state (not globalThis): a duplicated SSR chunk just means an extra\n * log line, which is harmless.\n */\nconst RUNTIME_INIT_ERROR_LOG_INTERVAL_MS = 30_000;\nlet lastRuntimeInitErrorLogAt = 0;\n\n/**\n * Whether we've verified the database has been set up.\n * On a fresh deployment the first request may hit a public page, bypassing\n * runtime init. Without this check, template helpers like getSiteSettings()\n * would query an empty database and crash. Once verified (or once the runtime\n * has initialized via an admin/API request), this stays true for the worker's\n * lifetime.\n *\n * Stored on globalThis behind a Symbol key so the flag is a true singleton\n * even when the bundler duplicates this module across SSR chunks (same\n * pattern as request-cache.ts). A plain module-scoped `let` becomes multiple\n * independent variables, which would make the setup probe re-run far more\n * often than intended — and every re-run is another chance for a transient\n * DB error to be misread as \"fresh install\" and bounce visitors to setup.\n */\nconst SETUP_VERIFIED_KEY = Symbol.for(\"emdash:setup-verified\");\nconst setupFlagStore = globalThis as Record<symbol, unknown>;\n\nfunction isSetupVerified(): boolean {\n\treturn setupFlagStore[SETUP_VERIFIED_KEY] === true;\n}\n\nfunction markSetupVerified(): void {\n\tsetupFlagStore[SETUP_VERIFIED_KEY] = true;\n}\n\n/**\n * The runtime singleton and its init lock live on globalThis behind a\n * Symbol — same reasoning as SETUP_VERIFIED_KEY above: the bundler can\n * duplicate this module across SSR chunks, and a duplicated instance/lock\n * would mean multiple runtimes (each with its own cron scheduler) per\n * isolate, initializing and reclaiming independently.\n */\nconst RUNTIME_HOLDER_KEY = Symbol.for(\"emdash:runtime-holder\");\ninterface RuntimeHolder {\n\tinstance: EmDashRuntime | null;\n\tlock: InitLock;\n}\n\nfunction getRuntimeHolder(): RuntimeHolder {\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- globalThis symbol slot, written only below\n\tlet holder = setupFlagStore[RUNTIME_HOLDER_KEY] as RuntimeHolder | undefined;\n\tif (!holder) {\n\t\tholder = { instance: null, lock: createInitLock() };\n\t\tsetupFlagStore[RUNTIME_HOLDER_KEY] = holder;\n\t}\n\treturn holder;\n}\n\n/** Whether i18n config has been initialized from the virtual module */\nlet i18nInitialized = false;\n\n/**\n * Get EmDash configuration from virtual module\n */\nfunction getConfig(): EmDashConfig | null {\n\tif (virtualConfig && typeof virtualConfig === \"object\") {\n\t\t// Initialize i18n config on first access (once per worker lifetime)\n\t\tif (!i18nInitialized) {\n\t\t\ti18nInitialized = true;\n\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- virtual module checked as object above\n\t\t\tconst config = virtualConfig as Record<string, unknown>;\n\t\t\tif (config.i18n && typeof config.i18n === \"object\") {\n\t\t\t\tsetI18nConfig(\n\t\t\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- runtime-checked above\n\t\t\t\t\tconfig.i18n as {\n\t\t\t\t\t\tdefaultLocale: string;\n\t\t\t\t\t\tlocales: string[];\n\t\t\t\t\t\tfallback?: Record<string, string>;\n\t\t\t\t\t},\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tsetI18nConfig(null);\n\t\t\t}\n\t\t}\n\n\t\treturn virtualConfig;\n\t}\n\treturn null;\n}\n\n/**\n * Get plugins from virtual module\n */\nfunction getPlugins(): ResolvedPlugin[] {\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- virtual module import is untyped (@ts-ignore above)\n\treturn (virtualPlugins as ResolvedPlugin[]) || [];\n}\n\n/**\n * Build runtime dependencies from virtual modules\n */\nfunction buildDependencies(\n\tconfig: EmDashConfig,\n\tmigrationMode: RuntimeMigrationMode,\n): RuntimeDependencies {\n\t/* eslint-disable typescript-eslint/no-unsafe-type-assertion --\n\t   The virtual:emdash/* imports above use @ts-ignore because tsgo/IDE\n\t   resolution can't see virtual-modules.d.ts in every consumer setup,\n\t   so they arrive as `any`. The casts here line each entry up with\n\t   RuntimeDependencies's expected shape. The contract is enforced by\n\t   the integration that populates these virtual modules. */\n\tconst sandboxModule = virtualSandboxRunnerModule as Record<string, unknown>;\n\treturn {\n\t\tconfig,\n\t\tmigrationMode,\n\t\tplugins: getPlugins(),\n\t\tcreateDialect: virtualCreateDialect as (config: Record<string, unknown>) => unknown,\n\t\t// Optional: only batching backends (D1, DO) export this; undefined otherwise.\n\t\tcreateCoalescingDialect: virtualCreateCoalescingDialect as\n\t\t\t| ((config: Record<string, unknown>) => unknown)\n\t\t\t| undefined,\n\t\tcreateStorage: virtualCreateStorage as ((config: Record<string, unknown>) => Storage) | null,\n\t\tcreateScheduler: virtualCreateScheduler as CreateSchedulerFn | null,\n\t\tsandboxEnabled: sandboxModule.sandboxEnabled as boolean,\n\t\tsandboxBypassed: (sandboxModule.sandboxBypassed as boolean) ?? false,\n\t\tsandboxedPluginEntries: (virtualSandboxedPlugins as SandboxedPluginEntry[]) || [],\n\t\tcreateSandboxRunner: sandboxModule.createSandboxRunner as SandboxRunnerFactory | null,\n\t\tmediaProviderEntries: (virtualMediaProviders as MediaProviderEntry[]) || [],\n\t};\n\t/* eslint-enable typescript-eslint/no-unsafe-type-assertion */\n}\n\n/**\n * Get or create the runtime instance.\n *\n * When `initTimings` is provided, any timing samples recorded during a\n * genuine cold init are appended. Subsequent warm calls (hitting the\n * cached instance) push nothing — callers should treat an empty array\n * as \"warm, nothing to report\".\n */\nasync function getRuntime(\n\tconfig: EmDashConfig,\n\tmigrationMode: RuntimeMigrationMode,\n\tinitTimings?: Array<{ name: string; dur: number; desc?: string }>,\n): Promise<EmDashRuntime> {\n\t// Waiters poll rather than awaiting the initializing request's promise —\n\t// workerd flags cross-request promise resolution (warnings + potential\n\t// hangs). If the initializing request is cancelled mid-create (client\n\t// disconnect tears down its continuation, skipping any `finally`), the\n\t// anchored init keeps running under waitUntil and populates the cache;\n\t// failing that, the stale lock is reclaimed after a deadline instead of\n\t// hanging every subsequent request in the isolate until eviction.\n\tconst holder = getRuntimeHolder();\n\treturn initWithLock(\n\t\tholder.lock,\n\t\t() => holder.instance,\n\t\tasync (isCurrentClaim) => {\n\t\t\tconst deps = buildDependencies(config, migrationMode);\n\t\t\tconst runtime = await EmDashRuntime.create(deps, initTimings);\n\t\t\tif (isCurrentClaim()) {\n\t\t\t\tholder.instance = runtime;\n\t\t\t} else {\n\t\t\t\t// This init was reclaimed mid-flight (it ran past the deadline\n\t\t\t\t// and a waiter started its own). Don't overwrite the\n\t\t\t\t// reclaimer's published runtime, and stop this one's cron\n\t\t\t\t// scheduler so it doesn't keep firing unreferenced. The\n\t\t\t\t// runtime is still returned — it's fully functional for the\n\t\t\t\t// request that built it.\n\t\t\t\truntime.stopCron().catch((error: unknown) => {\n\t\t\t\t\tconsole.error(\"[emdash] failed to stop superseded runtime's cron:\", error);\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn runtime;\n\t\t},\n\t\t{\n\t\t\tdeadlineMs: RUNTIME_INIT_DEADLINE_MS,\n\t\t\tanchor: (promise) => after(() => promise),\n\t\t},\n\t);\n}\n\n/**\n * Run scheduled maintenance (cron tasks, scheduled publishing, system cleanup)\n * outside any request. Resolves the runtime from the build-time virtual config\n * and the cached singleton — the same instance request handlers use.\n *\n * Wired into a platform heartbeat that is not a request: the Cloudflare Worker's\n * `scheduled()` handler (Cron Trigger) calls this. On Node the runtime's own\n * timer-based scheduler already drives the same work, so this isn't needed there.\n *\n * Returns the content promoted by the publishing sweep so the caller can purge\n * edge-cache tags for it. `onPublished` (optional) is awaited after each\n * collection's batch so the caller can invalidate edge-cache tags incrementally\n * rather than only after the whole sweep.\n */\nexport async function runScheduledTasks(\n\toptions: { onPublished?: (refs: PublishedRef[]) => Promise<void> } = {},\n): Promise<{ published: PublishedRef[] }> {\n\tconst config = getConfig();\n\tif (!config) return { published: [] };\n\treturn runOutsideRequest(config, (runtime) => runtime.runScheduledTasks(options));\n}\n\nexport async function runScheduledMediaUsageTasks(): Promise<MediaUsageMaintenanceResult> {\n\tconst config = getConfig();\n\tif (!config) return { outcome: \"inactive\", taskClass: null, turn: null };\n\treturn runOutsideRequest(config, (runtime) => runtime.runScheduledMediaUsageTasks());\n}\n\n/**\n * Run a callback against the EmDash runtime outside any HTTP request — from a\n * Cloudflare Queue consumer, a `scheduled()` handler, or any other\n * platform-event handler that has no request and therefore no `locals.emdash`.\n *\n * Resolves the same cached runtime singleton request handlers use, so hooks,\n * plugin state, and storage all behave exactly as they do during a request.\n * A typical queue consumer finishes a job by calling back into a plugin route:\n *\n * ```ts\n * import { withEmDashRuntime } from \"@premium-cms/emdash/middleware\";\n *\n * async function queue(batch: MessageBatch) {\n * \tawait withEmDashRuntime(async (runtime) => {\n * \t\tfor (const message of batch.messages) {\n * \t\t\tawait runtime.handlePluginApiRoute(\n * \t\t\t\t\"my-plugin\",\n * \t\t\t\t\"POST\",\n * \t\t\t\t\"/finishJob\",\n * \t\t\t\tnew Request(\"https://internal/\", {\n * \t\t\t\t\tmethod: \"POST\",\n * \t\t\t\t\tbody: JSON.stringify(message.body),\n * \t\t\t\t}),\n * \t\t\t);\n * \t\t}\n * \t});\n * }\n * ```\n *\n * Server-only and fully trusted: the callback gets the raw runtime with no\n * auth or CSRF checks, same trust level as plugin cron. Never expose it to\n * user input without validating that input yourself.\n *\n * Throws when EmDash is not configured (no `emdash()` Astro integration).\n */\nexport async function withEmDashRuntime<T>(\n\trun: (runtime: EmDashRuntime) => T | Promise<T>,\n): Promise<T> {\n\tconst config = getConfig();\n\tif (!config) {\n\t\tthrow new Error(\n\t\t\t\"EmDash is not configured — withEmDashRuntime() requires the emdash() Astro integration.\",\n\t\t);\n\t}\n\treturn runOutsideRequest(config, async (runtime) => run(runtime));\n}\n\n/**\n * Shared plumbing for request-free entry points (`runScheduledTasks`,\n * `withEmDashRuntime`): resolve the runtime singleton, then run the callback\n * under an event-scoped db when the adapter needs one.\n *\n * Connection-backed adapters (e.g. Postgres over Hyperdrive) cannot reuse\n * the per-isolate singleton from a platform event: its socket belongs to the\n * request that opened it, and workerd rejects cross-event I/O. Open an\n * event-scoped connection and run the callback under it in ALS — the\n * runtime's db getter, the cron executor, and plugin contexts all resolve\n * the connection from ALS — then close it when required. Stateless adapters\n * that need primary routing can return a close-less scope; adapters with no\n * event scoping return null and keep using the singleton.\n */\nasync function runOutsideRequest<T>(\n\tconfig: EmDashConfig,\n\tfn: (runtime: EmDashRuntime) => Promise<T>,\n): Promise<T> {\n\tconst migrationMode = resolveConfiguredMigrationMode(config);\n\tif (getRequestContext()) {\n\t\tconst runtime = await getRuntime(config, migrationMode);\n\t\treturn runOutsideRequestWithRuntime(config, runtime, fn);\n\t}\n\n\tconst deferredTasks = createDeferredTaskTracker(() => {});\n\tconst context = {\n\t\teditMode: false,\n\t\tmetrics: createRequestMetrics(performance.now()),\n\t\tdeferredTasks,\n\t};\n\treturn runWithContext(context, async () => {\n\t\tconst runtime = await (async () => {\n\t\t\ttry {\n\t\t\t\treturn await getRuntime(config, migrationMode);\n\t\t\t} finally {\n\t\t\t\tdeferredTasks.settle();\n\t\t\t\tawait deferredTasks.settled;\n\t\t\t}\n\t\t})();\n\t\treturn runOutsideRequestWithRuntime(config, runtime, fn);\n\t});\n}\n\nasync function runOutsideRequestWithRuntime<T>(\n\tconfig: EmDashConfig,\n\truntime: EmDashRuntime,\n\tfn: (runtime: EmDashRuntime) => Promise<T>,\n): Promise<T> {\n\tconst scoped = createRequestScopedDb({\n\t\tconfig: config.database?.config,\n\t\tisAuthenticated: false,\n\t\t// Event handlers publish, clean up, or run jobs — a write workload —\n\t\t// so a connection-backed adapter routes them to the primary.\n\t\tisWrite: true,\n\t\tcanUseCachedBinding: false,\n\t\tcookies: NOOP_COOKIE_JAR,\n\t\turl: CRON_EVENT_URL,\n\t});\n\tif (!scoped) {\n\t\t// This adapter needs no event-specific routing or connection.\n\t\treturn fn(runtime);\n\t}\n\tconst { closed, deferredTasks, lifecycle } = coordinateScopedDbLifecycle(scoped);\n\n\tconst parent = getRequestContext();\n\tconst ctx = parent\n\t\t? { ...parent, db: scoped.db, deferredTasks }\n\t\t: {\n\t\t\t\teditMode: false,\n\t\t\t\tdb: scoped.db,\n\t\t\t\tmetrics: createRequestMetrics(performance.now()),\n\t\t\t\tdeferredTasks,\n\t\t\t};\n\ttry {\n\t\treturn await runWithContext(ctx, () => fn(runtime));\n\t} finally {\n\t\t// Guard both so a throw in teardown can't mask the callback result or\n\t\t// skip lifecycle settlement and leak the connection.\n\t\ttry {\n\t\t\tlifecycle.commit();\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[emdash] event-scoped db commit failed:\", error);\n\t\t}\n\t\ttry {\n\t\t\tlifecycle.close?.();\n\t\t} catch (error) {\n\t\t\tconsole.error(\"[emdash] event-scoped db close failed:\", error);\n\t\t}\n\t\tawait closed;\n\t}\n}\n\n/**\n * A cookie jar that reads nothing and writes nothing, for request-scoped db\n * adapters invoked outside an HTTP request (the Cron Trigger sweep). Connection\n * adapters like Hyperdrive ignore cookies entirely; the D1 session adapter\n * reads/writes a bookmark cookie, but cron never reaches that path (it has no\n * `close()`), so the no-ops are never observed.\n */\nconst NOOP_COOKIE_JAR = {\n\tget: () => undefined,\n\tset: () => {},\n};\n\n/**\n * Synthetic URL for the cron sweep's request-scoped db opts. Only the D1\n * session adapter inspects `url` (for cookie `secure`), and cron doesn't take\n * that path, so the value is never used — it exists to satisfy the contract.\n */\nconst CRON_EVENT_URL = new URL(\"https://cron.emdash.internal/\");\n\nfunction resolveConfiguredMigrationMode(config: EmDashConfig): RuntimeMigrationMode {\n\tconst processOverride =\n\t\ttypeof process !== \"undefined\" && process.env ? process.env.EMDASH_MIGRATIONS_MODE : undefined;\n\tconst importMetaOverride = import.meta.env.EMDASH_MIGRATIONS_MODE;\n\treturn resolveRuntimeMigrationMode(config.migrations, {\n\t\tdev: import.meta.env.DEV,\n\t\toverride: processOverride ?? importMetaOverride,\n\t});\n}\n\nfunction pendingMigrationsResponse(error: PendingMigrationsError): Response {\n\tconsole.error(\"[emdash] database migrations are pending:\", error.pending.join(\", \"));\n\treturn migrationRequiredResponse();\n}\n\nfunction migrationRequiredResponse(): Response {\n\treturn new Response(\n\t\t\"Database migrations are required. Apply the deployment migration manifest and retry.\",\n\t\t{\n\t\t\tstatus: 503,\n\t\t\theaders: { \"Retry-After\": \"60\" },\n\t\t},\n\t);\n}\n\n/**\n * Baseline security headers applied to all responses.\n * Admin routes get additional headers (strict CSP) from auth middleware.\n */\nfunction finalizeResponse(\n\tresponse: Response,\n\tserverTimings?: Array<{ name: string; dur: number; desc?: string }>,\n): Response {\n\tconst res = new Response(response.body, response);\n\tconst astroCookies = Reflect.get(response, ASTRO_COOKIES_SYMBOL);\n\tif (astroCookies !== undefined) {\n\t\tReflect.set(res, ASTRO_COOKIES_SYMBOL, astroCookies);\n\t}\n\t// Set-if-absent so a host app that sets stricter values on its own routes\n\t// wins. The middleware registers `order: 'pre'` (#1282), so on the response\n\t// path it runs *after* host middleware; unconditional `set()` would clobber\n\t// the host's headers on every public route (#1393). Mirrors the CSP guard.\n\tif (!res.headers.has(\"X-Content-Type-Options\")) {\n\t\tres.headers.set(\"X-Content-Type-Options\", \"nosniff\");\n\t}\n\tif (!res.headers.has(\"Referrer-Policy\")) {\n\t\tres.headers.set(\"Referrer-Policy\", \"strict-origin-when-cross-origin\");\n\t}\n\tif (!res.headers.has(\"Permissions-Policy\")) {\n\t\tres.headers.set(\"Permissions-Policy\", \"camera=(), microphone=(), geolocation=(), payment=()\");\n\t}\n\tif (!res.headers.has(\"Content-Security-Policy\")) {\n\t\tres.headers.set(\"X-Frame-Options\", \"SAMEORIGIN\");\n\t}\n\tif (serverTimings && serverTimings.length > 0) {\n\t\tres.headers.set(\n\t\t\t\"Server-Timing\",\n\t\t\tserverTimings\n\t\t\t\t.map((t) => {\n\t\t\t\t\tconst dur = Math.round(t.dur);\n\t\t\t\t\treturn t.desc ? `${t.name};dur=${dur};desc=\"${t.desc}\"` : `${t.name};dur=${dur}`;\n\t\t\t\t})\n\t\t\t\t.join(\", \"),\n\t\t);\n\t}\n\treturn res;\n}\n\n/**\n * Append always-on counters (db.*, cache.*) to the Server-Timing list.\n *\n * dur values for `count`, `hit`, `miss` are integer counts — Server-Timing\n * spec only models milliseconds, but browsers show whatever number is given,\n * which is the convention most projects use for non-time samples.\n */\nfunction pushMetricsTimings(\n\ttimings: Array<{ name: string; dur: number; desc?: string }>,\n\tmetrics: RequestMetrics,\n): void {\n\tif (metrics.dbCount > 0) {\n\t\ttimings.push({ name: \"db.total\", dur: metrics.dbTotalMs, desc: \"DB total\" });\n\t\ttimings.push({ name: \"db.count\", dur: metrics.dbCount, desc: \"Query count\" });\n\t\tif (metrics.dbFirstOffset !== null) {\n\t\t\ttimings.push({ name: \"db.first\", dur: metrics.dbFirstOffset, desc: \"First query at\" });\n\t\t}\n\t\tif (metrics.dbLastOffset !== null) {\n\t\t\ttimings.push({ name: \"db.last\", dur: metrics.dbLastOffset, desc: \"Last query at\" });\n\t\t}\n\t}\n\tif (metrics.rpcCount > 0) {\n\t\ttimings.push({ name: \"rpc.count\", dur: metrics.rpcCount, desc: \"DB round trips\" });\n\t}\n\tif (metrics.cacheHits + metrics.cacheMisses > 0) {\n\t\ttimings.push({ name: \"cache.hit\", dur: metrics.cacheHits, desc: \"Cache hits\" });\n\t\ttimings.push({ name: \"cache.miss\", dur: metrics.cacheMisses, desc: \"Cache misses\" });\n\t}\n}\n\n/** Public routes that require the runtime (sitemap, robots.txt, etc.) */\nconst PUBLIC_RUNTIME_ROUTES = new Set([\"/sitemap.xml\", \"/robots.txt\"]);\nconst SITEMAP_COLLECTION_RE = /^\\/sitemap-[a-z][a-z0-9_]*\\.xml$/;\n\n/**\n * Ask the configured database adapter for a per-request scoped Kysely. The\n * adapter encapsulates any per-request semantics (D1 sessions, read-replica\n * routing, bookmark cookies, etc.); core just forwards the cookie jar and\n * request flags and wraps next() in ALS if a scope was returned.\n */\nfunction createRequestScopedDb(\n\topts: RequestScopedDbOpts,\n): { db: Kysely<Database>; commit: () => void; close?: () => void } | null {\n\tif (typeof virtualCreateRequestScopedDb !== \"function\") return null;\n\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- adapter returns Kysely<unknown>; cast to Database since core owns that type\n\tconst fn = virtualCreateRequestScopedDb as (\n\t\to: RequestScopedDbOpts,\n\t) => { db: Kysely<Database>; commit: () => void; close?: () => void } | null;\n\treturn fn(opts);\n}\n\nconst buildDate = virtualBuildTime ? new Date(virtualBuildTime) : null;\n\n/**\n * Fold the build timestamp into the route cache validator.\n *\n * `CacheHint.lastModified` describes the content, but the response also depends\n * on the build: `/_astro/*` names are content-hashed, and a deployment only\n * serves its own. Without the build dimension a code-only deploy answers a\n * returning visitor's conditional request with 304, leaving them on HTML whose\n * assets 404.\n *\n * Prerendered pages are served by the host's static layer, which manages its\n * own validators — only on-demand responses need the build dimension.\n *\n * Only forward moves are covered. `Last-Modified` expresses newer, not\n * different, so after a rollback the earlier build still answers a conditional\n * request with 304 and the browser stays on the newer build's HTML.\n *\n * Must run before next(): Astro keeps the later of two dates, so a route's own\n * hint still wins when content is newer, and a route that opts out with\n * `Astro.cache.set(false)` stays opted out — calling set() afterwards would\n * clear that opt-out.\n */\nfunction applyBuildValidator(context: APIContext): void {\n\tif (context.isPrerendered || !buildDate || !context.cache?.enabled) return;\n\tcontext.cache.set({ lastModified: buildDate });\n}\n\nexport const onRequest = defineMiddleware(async (context, next) => {\n\tconst { request, locals, cookies } = context;\n\tconst url = context.url;\n\n\t// Fast path: routes outside /_emdash/ that plugins inject (e.g.,\n\t// /.well-known/atproto-client-metadata.json) skip the entire runtime\n\t// init + middleware chain. External servers fetch these with tight\n\t// timeouts (~1-2s) so they must respond quickly even on cold starts.\n\tif (!url.pathname.startsWith(\"/_emdash\") && virtualConfig?.authProviders) {\n\t\tconst isPluginFastRoute = virtualConfig.authProviders.some(\n\t\t\t(p: { routes?: { pattern?: string }[] }) =>\n\t\t\t\tp.routes?.some((r: { pattern?: string }) => r.pattern && url.pathname === r.pattern),\n\t\t);\n\t\tif (isPluginFastRoute) {\n\t\t\treturn finalizeResponse(await next());\n\t\t}\n\t}\n\n\tapplyBuildValidator(context);\n\n\tconst queryRecorder = isInstrumentationEnabled()\n\t\t? createRecorder(url.pathname, request.method, request.headers.get(\"x-perf-phase\") ?? \"default\")\n\t\t: undefined;\n\n\tconst metrics = createRequestMetrics(performance.now());\n\n\tconst run = async (): Promise<Response> => {\n\t\tconst config = getConfig();\n\t\tconst migrationMode = config ? resolveConfiguredMigrationMode(config) : \"auto\";\n\t\t// Process /_emdash routes and public routes with an active session\n\t\t// (logged-in editors need the runtime for toolbar/visual editing on public pages)\n\t\tconst isEmDashRoute = url.pathname.startsWith(\"/_emdash\");\n\t\tconst isPublicRuntimeRoute =\n\t\t\tPUBLIC_RUNTIME_ROUTES.has(url.pathname) || SITEMAP_COLLECTION_RE.test(url.pathname);\n\n\t\t// Check for edit mode cookie - editors viewing public pages need the runtime\n\t\t// so auth middleware can verify their session for visual editing\n\t\tconst hasEditCookie = cookies.get(\"emdash-edit-mode\")?.value === \"true\";\n\t\tconst hasPreviewToken = url.searchParams.has(\"_preview\");\n\n\t\t// Playground mode: the playground middleware stashes the per-session DO database\n\t\t// on locals.__playgroundDb. When present, use runWithContext() to make it\n\t\t// available to getDb() and the runtime's db getter via the correct ALS instance.\n\t\tconst playgroundDb = locals.__playgroundDb;\n\n\t\t// Read the Astro session user once up-front. Both the anonymous fast path\n\t\t// and the full doInit path need this, and the session store is network-backed\n\t\t// (KV / Durable Object) so we want to avoid re-fetching on the hot path.\n\t\t// Skipped entirely for:\n\t\t//   - prerendered requests (no session at build time)\n\t\t//   - requests without an `astro-session` cookie (no session to look up)\n\t\t// The cookie check matters on Cloudflare Workers, where Astro's session\n\t\t// backend is KV: calling session.get() on every anonymous public request\n\t\t// turns normal traffic into a flood of KV read misses. See #733.\n\t\tconst hasSessionCookie = cookies.get(\"astro-session\") !== undefined;\n\t\tconst sessionUser =\n\t\t\tcontext.isPrerendered || !hasSessionCookie ? null : await resolveSessionUser(context.session);\n\n\t\t// Credentialed API requests (API tokens `ec_pat_*`, OAuth tokens\n\t\t// `ec_oat_*`, and other Bearer credentials) carry no `astro-session`\n\t\t// cookie, so `sessionUser` is null for them -- yet they still expect\n\t\t// read-your-writes. The auth middleware that resolves the token runs\n\t\t// *after* this one, so `locals.user` isn't populated here; detect the\n\t\t// credential directly on the request. Request-scoped adapters use this to\n\t\t// keep such requests on the primary/uncached connection (not a lagging\n\t\t// read replica or the Hyperdrive query cache).\n\t\tconst hasBearerAuth = (request.headers.get(\"authorization\") ?? \"\")\n\t\t\t.toLowerCase()\n\t\t\t.startsWith(\"bearer \");\n\t\tconst isWrite = request.method !== \"GET\" && request.method !== \"HEAD\";\n\t\tconst isAuthenticated = !!sessionUser || hasBearerAuth;\n\t\tconst endedAuthenticated = () => requestEndedAuthenticated(isAuthenticated, cookies);\n\t\tconst canUseCachedBinding =\n\t\t\t!isAuthenticated &&\n\t\t\t!isWrite &&\n\t\t\t!playgroundDb &&\n\t\t\t!isEmDashRoute &&\n\t\t\t!hasEditCookie &&\n\t\t\t!hasPreviewToken;\n\n\t\tif (!isEmDashRoute && !isPublicRuntimeRoute && !hasEditCookie && !hasPreviewToken) {\n\t\t\tif (!sessionUser && !playgroundDb) {\n\t\t\t\tconst timings: Array<{ name: string; dur: number; desc?: string }> = [];\n\t\t\t\tconst mwStart = performance.now();\n\n\t\t\t\t// On a fresh deployment the database may be completely empty.\n\t\t\t\t// Public pages call getSiteSettings() / getMenu() via getDb(), which\n\t\t\t\t// bypasses runtime init and would crash with \"no such table: options\".\n\t\t\t\t// Do a one-time lightweight probe using the same getDb() instance the\n\t\t\t\t// page will use: if the migrations table doesn't exist, no migrations\n\t\t\t\t// have ever run -- redirect to the setup wizard.\n\t\t\t\t// Skip the probe when prerendering: a prerendered route is built to\n\t\t\t\t// static HTML, so returning context.redirect(\"/_emdash/admin/setup\")\n\t\t\t\t// below would bake that redirect into the page and ship it to\n\t\t\t\t// production. The build database is legitimately empty in CI and there\n\t\t\t\t// is no live visitor to send to the wizard at build time (session reads\n\t\t\t\t// are already skipped for prerender above for the same reason).\n\t\t\t\tif (migrationMode === \"auto\" && !isSetupVerified() && !context.isPrerendered) {\n\t\t\t\t\tconst t0 = performance.now();\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst { getDb } = await import(\"../loader.js\");\n\t\t\t\t\t\tconst db = await getDb();\n\t\t\t\t\t\tawait db.selectFrom(\"_emdash_migrations\").selectAll().limit(1).execute();\n\t\t\t\t\t\tmarkSetupVerified();\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t// Only a genuinely-missing migrations table means a fresh,\n\t\t\t\t\t\t// un-set-up database — redirect to the setup wizard.\n\t\t\t\t\t\tif (isMissingTableError(error)) {\n\t\t\t\t\t\t\treturn context.redirect(\"/_emdash/admin/setup\");\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Any other failure (transient D1/replica error, timeout, cold-start\n\t\t\t\t\t\t// race, locked SQLite) must NOT be read as \"fresh install\" — doing so\n\t\t\t\t\t\t// bounces real visitors on a set-up site to /_emdash/admin/setup.\n\t\t\t\t\t\t// Leave the flag unset so a later request can re-verify, and fall\n\t\t\t\t\t\t// through to render the page normally.\n\t\t\t\t\t\tconsole.error(\"Setup probe failed (non-fatal):\", error);\n\t\t\t\t\t}\n\t\t\t\t\ttimings.push({ name: \"setup\", dur: performance.now() - t0, desc: \"Setup probe\" });\n\t\t\t\t}\n\n\t\t\t\t// Initialize the runtime for page:metadata and page:fragments hooks.\n\t\t\t\t// The runtime is a cached singleton — after the first request,\n\t\t\t\t// getRuntime() is just a null-check. This enables SEO plugins to\n\t\t\t\t// contribute meta tags for all visitors, not just logged-in editors.\n\t\t\t\tif (config) {\n\t\t\t\t\t// Sub-phase timings are populated only on the cold init. Warm\n\t\t\t\t\t// requests hit the cached runtime and leave this empty.\n\t\t\t\t\tconst initSubTimings: Array<{ name: string; dur: number; desc?: string }> = [];\n\t\t\t\t\tconst t0 = performance.now();\n\t\t\t\t\ttry {\n\t\t\t\t\t\tconst runtime = await getRuntime(config, migrationMode, initSubTimings);\n\t\t\t\t\t\tmarkSetupVerified();\n\t\t\t\t\t\tconst handlePublicPluginApiRoute = createPublicPluginApiRouteHandler(runtime);\n\t\t\t\t\t\t// eslint-disable-next-line typescript/no-unsafe-type-assertion -- partial object; getPageRuntime() only checks for the page-contribution methods\n\t\t\t\t\t\tlocals.emdash = {\n\t\t\t\t\t\t\thandlePublicPluginApiRoute,\n\t\t\t\t\t\t\tcollectPageMetadata: runtime.collectPageMetadata.bind(runtime),\n\t\t\t\t\t\t\tcollectPageFragments: runtime.collectPageFragments.bind(runtime),\n\t\t\t\t\t\t\tgetPublicMediaUrl: createPublicMediaUrlResolver(runtime.storage),\n\t\t\t\t\t\t\t// Exposed so the wrapped image endpoint (`/_image`) can read media\n\t\t\t\t\t\t\t// bytes from storage on the anonymous fast path -- public `<img>`\n\t\t\t\t\t\t\t// requests carry no session.\n\t\t\t\t\t\t\tstorage: runtime.storage,\n\t\t\t\t\t\t} as EmDashHandlers;\n\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\tif (error instanceof PendingMigrationsError) {\n\t\t\t\t\t\t\treturn pendingMigrationsResponse(error);\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (migrationMode === \"manual\" && isMissingTableError(error)) {\n\t\t\t\t\t\t\tconsole.error(\n\t\t\t\t\t\t\t\t\"[emdash] database schema is unavailable in manual migration mode:\",\n\t\t\t\t\t\t\t\terror,\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\treturn migrationRequiredResponse();\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// Non-fatal — EmDashHead falls back to base SEO contributions —\n\t\t\t\t\t\t// but log it (throttled): a persistently failing init (e.g. a\n\t\t\t\t\t\t// failing migration, #1744) is otherwise invisible on the\n\t\t\t\t\t\t// anonymous path, silently degrading every public page.\n\t\t\t\t\t\tif (Date.now() - lastRuntimeInitErrorLogAt >= RUNTIME_INIT_ERROR_LOG_INTERVAL_MS) {\n\t\t\t\t\t\t\tlastRuntimeInitErrorLogAt = Date.now();\n\t\t\t\t\t\t\tconsole.error(\"[emdash] runtime init failed (page renders without CMS data):\", error);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\ttimings.push({ name: \"rt\", dur: performance.now() - t0, desc: \"Runtime init\" });\n\t\t\t\t\t// Append cold-only sub-phase timings so the breakdown is visible\n\t\t\t\t\t// in Server-Timing (rt.db, rt.fts, rt.plugins, rt.site,\n\t\t\t\t\t// rt.sandbox, rt.market, rt.hooks, rt.cron).\n\t\t\t\t\tfor (const sub of initSubTimings) timings.push(sub);\n\t\t\t\t}\n\n\t\t\t\t// Even on the anonymous fast path we ask the adapter for a per-request\n\t\t\t\t// scoped db. For D1 with read replication this routes anonymous reads\n\t\t\t\t// to the nearest replica; for other adapters it's a no-op.\n\t\t\t\tconst lastContentWriteAt =\n\t\t\t\t\tcanUseCachedBinding && config?.database?.needsLastContentWriteAt\n\t\t\t\t\t\t? await getLastContentWriteAt()\n\t\t\t\t\t\t: undefined;\n\t\t\t\tconst anonScoped = createRequestScopedDb({\n\t\t\t\t\tconfig: config?.database?.config,\n\t\t\t\t\tisAuthenticated,\n\t\t\t\t\tendedAuthenticated,\n\t\t\t\t\tisWrite,\n\t\t\t\t\tcanUseCachedBinding,\n\t\t\t\t\tcookies,\n\t\t\t\t\turl,\n\t\t\t\t\tlastContentWriteAt,\n\t\t\t\t});\n\t\t\t\tconst runAnon = async () => {\n\t\t\t\t\tconst t0 = performance.now();\n\t\t\t\t\tconst response = await next();\n\t\t\t\t\ttimings.push({ name: \"render\", dur: performance.now() - t0, desc: \"Page render\" });\n\t\t\t\t\ttimings.push({ name: \"mw\", dur: performance.now() - mwStart, desc: \"Total middleware\" });\n\t\t\t\t\tpushMetricsTimings(timings, metrics);\n\t\t\t\t\t// Server-Timing only sees pre-stream queries; the stream-end\n\t\t\t\t\t// wrapper (instrumentation-gated, no-op otherwise) emits the\n\t\t\t\t\t// final counters once the body finishes streaming.\n\t\t\t\t\treturn wrapBodyForStreamMetrics(finalizeResponse(response, timings));\n\t\t\t\t};\n\t\t\t\tif (anonScoped) {\n\t\t\t\t\tconst { deferredTasks, lifecycle } = coordinateScopedDbLifecycle(anonScoped);\n\t\t\t\t\tconst parent = getRequestContext();\n\t\t\t\t\tconst ctx = parent\n\t\t\t\t\t\t? { ...parent, db: anonScoped.db, deferredTasks }\n\t\t\t\t\t\t: { editMode: false, db: anonScoped.db, metrics, deferredTasks };\n\t\t\t\t\t// Eagerly warm site-global layout data (menus, widget areas,\n\t\t\t\t\t// taxonomy terms, settings) concurrently so the layout's\n\t\t\t\t\t// per-component reads overlap into ~one wall-clock round trip and\n\t\t\t\t\t// hit a warm cache instead of serializing. Three guards:\n\t\t\t\t\t//  - request-scoped (remote) backend only -- this branch implies it;\n\t\t\t\t\t//    pointless on synchronous local SQLite.\n\t\t\t\t\t//  - HTML navigations only -- feeds/sitemaps/JSON don't render the\n\t\t\t\t\t//    layout, so prefetching their chrome is pure waste.\n\t\t\t\t\t//  - the work starts immediately, then after() keeps both it and the\n\t\t\t\t\t//    request-scoped connection alive until the response also finishes.\n\t\t\t\t\t// Gate on the CLIENT'S PREFERRED type (leading media range), not a\n\t\t\t\t\t// substring -- browser navigations lead with `text/html`, while feed\n\t\t\t\t\t// readers lead with `application/rss+xml` etc. and only list\n\t\t\t\t\t// `text/html;q=0.8` later, so a substring match would leak onto feeds.\n\t\t\t\t\tconst acceptsHtml = (request.headers.get(\"accept\") ?? \"\")\n\t\t\t\t\t\t.split(\",\", 1)[0]!\n\t\t\t\t\t\t.trim()\n\t\t\t\t\t\t.startsWith(\"text/html\");\n\t\t\t\t\treturn runWithContext(ctx, async () => {\n\t\t\t\t\t\tif (acceptsHtml) after(() => prefetchLayoutData());\n\t\t\t\t\t\t// commit() persists per-request state (e.g. the D1 bookmark cookie)\n\t\t\t\t\t\t// before the response is returned, even if render throws; close()\n\t\t\t\t\t\t// waits for stream-end and request-owned deferred work. See finishScoped.\n\t\t\t\t\t\treturn finishScoped(lifecycle, runAnon);\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn runAnon();\n\t\t\t}\n\t\t}\n\n\t\tif (!config) {\n\t\t\tconsole.error(\"EmDash: No configuration found\");\n\t\t\treturn finalizeResponse(await next());\n\t\t}\n\n\t\t// In playground mode, wrap the entire runtime init + request handling in\n\t\t// runWithContext so that getDatabase() and all init queries use the real\n\t\t// DO database via the same AsyncLocalStorage instance as the loader.\n\t\tconst doInit = async () => {\n\t\t\tconst timings: Array<{ name: string; dur: number; desc?: string }> = [];\n\t\t\tconst mwStart = performance.now();\n\n\t\t\ttry {\n\t\t\t\t// Get or create runtime. Sub-phase timings (rt.db, rt.fts, rt.plugins,\n\t\t\t\t// rt.site, rt.sandbox, rt.market, rt.hooks, rt.cron) are populated\n\t\t\t\t// only on the cold init — subsequent warm calls find the cached\n\t\t\t\t// instance and `initSubTimings` stays empty.\n\t\t\t\tconst initSubTimings: Array<{ name: string; dur: number; desc?: string }> = [];\n\t\t\t\tlet t0 = performance.now();\n\t\t\t\tconst runtime = await getRuntime(config, migrationMode, initSubTimings);\n\t\t\t\ttimings.push({ name: \"rt\", dur: performance.now() - t0, desc: \"Runtime init\" });\n\t\t\t\t// Plugin updates land in one isolate; the others catch up here (throttled, one small query).\n\t\t\t\tawait runtime.resyncPluginsIfStale?.();\n\t\t\t\t// Forward any sub-phase samples so cold-start breakdown is visible\n\t\t\t\t// in Server-Timing. Each phase appears prefixed \"rt.\" to distinguish\n\t\t\t\t// from the aggregate \"rt\" timing above.\n\t\t\t\tfor (const sub of initSubTimings) timings.push(sub);\n\n\t\t\t\t// Runtime initialization has satisfied the effective migration policy.\n\t\t\t\tmarkSetupVerified();\n\n\t\t\t\t// The manifest is no longer pre-loaded here. It's admin-only\n\t\t\t\t// content that public/anonymous requests never read, and\n\t\t\t\t// loading it on every request put logged-out hot paths on\n\t\t\t\t// the same staleness budget as admin operations. Admin\n\t\t\t\t// routes call `emdash.getManifest()` directly.\n\n\t\t\t\t// Attach to locals for route handlers\n\t\t\t\tlocals.emdash = {\n\t\t\t\t\t// Content handlers\n\t\t\t\t\thandleContentList: runtime.handleContentList.bind(runtime),\n\t\t\t\t\thandleContentGet: runtime.handleContentGet.bind(runtime),\n\t\t\t\t\thandleContentAuthors: runtime.handleContentAuthors.bind(runtime),\n\t\t\t\t\thandleContentCreate: runtime.handleContentCreate.bind(runtime),\n\t\t\t\t\thandleContentUpdate: runtime.handleContentUpdate.bind(runtime),\n\t\t\t\t\thandleContentDelete: runtime.handleContentDelete.bind(runtime),\n\n\t\t\t\t\t// Trash handlers\n\t\t\t\t\thandleContentListTrashed: runtime.handleContentListTrashed.bind(runtime),\n\t\t\t\t\thandleContentRestore: runtime.handleContentRestore.bind(runtime),\n\t\t\t\t\thandleContentPermanentDelete: runtime.handleContentPermanentDelete.bind(runtime),\n\t\t\t\t\thandleContentCountTrashed: runtime.handleContentCountTrashed.bind(runtime),\n\t\t\t\t\thandleContentGetIncludingTrashed: runtime.handleContentGetIncludingTrashed.bind(runtime),\n\n\t\t\t\t\t// Duplicate handler\n\t\t\t\t\thandleContentDuplicate: runtime.handleContentDuplicate.bind(runtime),\n\n\t\t\t\t\t// Publishing & Scheduling handlers\n\t\t\t\t\thandleContentPublish: runtime.handleContentPublish.bind(runtime),\n\t\t\t\t\thandleContentUnpublish: runtime.handleContentUnpublish.bind(runtime),\n\t\t\t\t\thandleContentSchedule: runtime.handleContentSchedule.bind(runtime),\n\t\t\t\t\thandleContentUnschedule: runtime.handleContentUnschedule.bind(runtime),\n\t\t\t\t\thandleContentCountScheduled: runtime.handleContentCountScheduled.bind(runtime),\n\t\t\t\t\thandleContentDiscardDraft: runtime.handleContentDiscardDraft.bind(runtime),\n\t\t\t\t\thandleContentCompare: runtime.handleContentCompare.bind(runtime),\n\t\t\t\t\thandleContentTranslations: runtime.handleContentTranslations.bind(runtime),\n\n\t\t\t\t\t// Media handlers\n\t\t\t\t\thandleMediaList: runtime.handleMediaList.bind(runtime),\n\t\t\t\t\thandleMediaGet: runtime.handleMediaGet.bind(runtime),\n\t\t\t\t\thandleMediaCreate: runtime.handleMediaCreate.bind(runtime),\n\t\t\t\t\thandleMediaUpdate: runtime.handleMediaUpdate.bind(runtime),\n\t\t\t\t\thandleMediaDelete: runtime.handleMediaDelete.bind(runtime),\n\n\t\t\t\t\t// Revision handlers\n\t\t\t\t\thandleRevisionList: runtime.handleRevisionList.bind(runtime),\n\t\t\t\t\thandleRevisionGet: runtime.handleRevisionGet.bind(runtime),\n\t\t\t\t\thandleRevisionRestore: runtime.handleRevisionRestore.bind(runtime),\n\n\t\t\t\t\t// Plugin routes\n\t\t\t\t\thandlePluginApiRoute: runtime.handlePluginApiRoute.bind(runtime),\n\t\t\t\t\thandlePublicPluginApiRoute: createPublicPluginApiRouteHandler(runtime),\n\t\t\t\t\tgetPluginRouteMeta: runtime.getPluginRouteMeta.bind(runtime),\n\t\t\t\t\tlistPluginRoutes: runtime.listPluginRoutes.bind(runtime),\n\t\t\t\t\tgetRuntimePluginSettingsSchema: runtime.getRuntimePluginSettingsSchema.bind(runtime),\n\t\t\t\t\tgetPluginMcpTools: runtime.getPluginMcpTools.bind(runtime),\n\t\t\t\t\tgetEnabledPluginMcpTools: runtime.getEnabledPluginMcpTools.bind(runtime),\n\t\t\t\t\tserializePluginMcpConsent: runtime.serializePluginMcpConsent.bind(runtime),\n\t\t\t\t\thandlePluginMcpTool: runtime.handlePluginMcpTool.bind(runtime),\n\t\t\t\t\thandlePluginMcpDenied: runtime.handlePluginMcpDenied.bind(runtime),\n\n\t\t\t\t\t// Media provider methods\n\t\t\t\t\tgetMediaProvider: runtime.getMediaProvider.bind(runtime),\n\t\t\t\t\tgetMediaProviderList: runtime.getMediaProviderList.bind(runtime),\n\n\t\t\t\t\t// Page contribution methods (for EmDashHead/EmDashBodyStart/EmDashBodyEnd)\n\t\t\t\t\tcollectPageMetadata: runtime.collectPageMetadata.bind(runtime),\n\t\t\t\t\tcollectPageFragments: runtime.collectPageFragments.bind(runtime),\n\n\t\t\t\t\t// Lazy search index health check — search endpoints call this\n\t\t\t\t\t// before querying so a crash-corrupted index gets repaired on\n\t\t\t\t\t// first use rather than stalling every cold start.\n\t\t\t\t\tensureSearchHealthy: runtime.ensureSearchHealthy.bind(runtime),\n\n\t\t\t\t\t// Direct access (for advanced use cases)\n\t\t\t\t\tstorage: runtime.storage,\n\t\t\t\t\t// Lazy getter, not an eager snapshot: `locals.emdash` is built\n\t\t\t\t\t// before the per-request scoped db is installed in ALS, so reading\n\t\t\t\t\t// `runtime.db` here would capture the per-isolate singleton. Routes\n\t\t\t\t\t// access `emdash.db` later, during the request, when the scoped db\n\t\t\t\t\t// is active. For a stateless binding (D1) the two are equivalent,\n\t\t\t\t\t// but for a request-bound connection (pg/Hyperdrive) the singleton\n\t\t\t\t\t// belongs to the cold-start request and reusing it from a warm\n\t\t\t\t\t// request hangs on workerd's cross-request I/O guard.\n\t\t\t\t\tget db() {\n\t\t\t\t\t\treturn runtime.db;\n\t\t\t\t\t},\n\t\t\t\t\tgetPublicMediaUrl: createPublicMediaUrlResolver(runtime.storage),\n\t\t\t\t\thooks: runtime.hooks,\n\t\t\t\t\temail: runtime.email,\n\t\t\t\t\tconfiguredPlugins: runtime.configuredPlugins,\n\t\t\t\t\tsandboxedPluginEntries: runtime.sandboxedPluginEntries,\n\n\t\t\t\t\t// Configuration (for checking database type, auth mode, etc.)\n\t\t\t\t\tconfig,\n\n\t\t\t\t\t// Lazy manifest accessor — admin-only consumers call this on\n\t\t\t\t\t// demand. `requestCached` inside `getManifest` dedupes within\n\t\t\t\t\t// a single request.\n\t\t\t\t\tgetManifest: runtime.getManifest.bind(runtime),\n\n\t\t\t\t\t// Clear the URL pattern cache after schema mutations that\n\t\t\t\t\t// affect collection URL patterns.\n\t\t\t\t\tinvalidateUrlPatternCache,\n\n\t\t\t\t\t// Sandbox runner (for marketplace plugin install/update)\n\t\t\t\t\tgetSandboxRunner: runtime.getSandboxRunner.bind(runtime),\n\t\t\t\t\tisSandboxBypassed: runtime.isSandboxBypassed.bind(runtime),\n\n\t\t\t\t\t// Sync marketplace plugin states (after install/update/uninstall)\n\t\t\t\t\tsyncMarketplacePlugins: runtime.syncMarketplacePlugins.bind(runtime),\n\n\t\t\t\t\t// Sync registry plugin states (after install/update/uninstall)\n\t\t\t\t\tsyncRegistryPlugins: runtime.syncRegistryPlugins.bind(runtime),\n\n\t\t\t\t\t// Update plugin enabled/disabled status and rebuild hook pipeline\n\t\t\t\t\tsetPluginStatus: runtime.setPluginStatus.bind(runtime),\n\t\t\t\t};\n\t\t\t} catch (error) {\n\t\t\t\tif (error instanceof PendingMigrationsError) {\n\t\t\t\t\treturn pendingMigrationsResponse(error);\n\t\t\t\t}\n\t\t\t\tif (migrationMode === \"manual\" && isMissingTableError(error)) {\n\t\t\t\t\tconsole.error(\"[emdash] database schema is unavailable in manual migration mode:\", error);\n\t\t\t\t\treturn migrationRequiredResponse();\n\t\t\t\t}\n\t\t\t\tconsole.error(\"EmDash middleware error:\", error);\n\t\t\t}\n\n\t\t\t// Ask the adapter for a request-scoped db. When it returns one, we stash\n\t\t\t// it in ALS so the runtime's db getter and loader's getDb() pick it up,\n\t\t\t// then call commit() after next() so the adapter can persist any\n\t\t\t// per-request state (e.g. a D1 bookmark cookie for read-your-writes).\n\t\t\tconst lastContentWriteAt =\n\t\t\t\tcanUseCachedBinding && config?.database?.needsLastContentWriteAt\n\t\t\t\t\t? await getLastContentWriteAt()\n\t\t\t\t\t: undefined;\n\t\t\tconst scoped = createRequestScopedDb({\n\t\t\t\tconfig: config?.database?.config,\n\t\t\t\tisAuthenticated,\n\t\t\t\tendedAuthenticated,\n\t\t\t\tisWrite,\n\t\t\t\tcanUseCachedBinding,\n\t\t\t\tcookies: context.cookies,\n\t\t\t\turl,\n\t\t\t\tlastContentWriteAt,\n\t\t\t});\n\n\t\t\tconst renderAndFinalize = async () => {\n\t\t\t\tconst t0 = performance.now();\n\t\t\t\tconst response = await next();\n\t\t\t\ttimings.push({ name: \"render\", dur: performance.now() - t0, desc: \"Page render\" });\n\t\t\t\ttimings.push({ name: \"mw\", dur: performance.now() - mwStart, desc: \"Total middleware\" });\n\t\t\t\tpushMetricsTimings(timings, metrics);\n\t\t\t\t// Server-Timing only sees pre-stream queries; the stream-end\n\t\t\t\t// wrapper (instrumentation-gated, no-op otherwise) emits the\n\t\t\t\t// final counters once the body finishes streaming.\n\t\t\t\treturn wrapBodyForStreamMetrics(finalizeResponse(response, timings));\n\t\t\t};\n\n\t\t\tif (scoped) {\n\t\t\t\tconst { deferredTasks, lifecycle } = coordinateScopedDbLifecycle(scoped);\n\t\t\t\tconst parent = getRequestContext();\n\t\t\t\tconst ctx = parent\n\t\t\t\t\t? { ...parent, db: scoped.db, deferredTasks }\n\t\t\t\t\t: { editMode: false, db: scoped.db, metrics, deferredTasks };\n\t\t\t\treturn runWithContext(ctx, () =>\n\t\t\t\t\t// commit() persists per-request state (e.g. the D1 bookmark cookie)\n\t\t\t\t\t// before the response returns, even if render throws; close()\n\t\t\t\t\t// waits for stream-end and request-owned deferred work. See finishScoped.\n\t\t\t\t\tfinishScoped(lifecycle, renderAndFinalize),\n\t\t\t\t);\n\t\t\t}\n\n\t\t\treturn renderAndFinalize();\n\t\t}; // end doInit\n\n\t\tif (playgroundDb) {\n\t\t\t// Read the edit-mode cookie to determine if visual editing is active.\n\t\t\t// Default to false -- editing is opt-in via the playground toolbar toggle.\n\t\t\tconst editMode = context.cookies.get(\"emdash-edit-mode\")?.value === \"true\";\n\t\t\t// Playground DBs are per-session isolated instances whose schema is\n\t\t\t// independent of the configured one — flag as isolated so schema-\n\t\t\t// derived caches (manifest, taxonomy defs) rebuild against it.\n\t\t\tconst parent = getRequestContext();\n\t\t\tconst ctx = parent\n\t\t\t\t? { ...parent, editMode, db: playgroundDb, dbIsIsolated: true }\n\t\t\t\t: { editMode, db: playgroundDb, dbIsIsolated: true, metrics };\n\t\t\treturn runWithContext(ctx, doInit);\n\t\t}\n\t\treturn doInit();\n\t};\n\n\ttry {\n\t\treturn await runWithContext({ editMode: false, queryRecorder, metrics }, run);\n\t} finally {\n\t\t// Streamed responses defer the flush to stream end (see\n\t\t// wrapBodyForStreamMetrics) so the log captures queries issued while\n\t\t// the body renders. Only flush here for responses that were not\n\t\t// wrapped (no body: redirects, 304s, bodyless errors), where all\n\t\t// queries have already run by the time middleware returns.\n\t\tif (queryRecorder && !queryRecorder.deferredFlush) flushRecorder(queryRecorder);\n\t}\n});\n\nexport default onRequest;\n"],"mappings":"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"}