Skip to content

feat(core): add reliable incremental media usage indexing - #2394

Merged
khoinguyenpham04 merged 11 commits into
fix/media-usage-cleanup-sequence-0from
feature/media-usage-sequence-1
Aug 11, 2026
Merged

feat(core): add reliable incremental media usage indexing#2394
khoinguyenpham04 merged 11 commits into
fix/media-usage-cleanup-sequence-0from
feature/media-usage-sequence-1

Conversation

@khoinguyenpham04

@khoinguyenpham04 khoinguyenpham04 commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Adds durable, bounded incremental Media Usage indexing on top of Sequence 0. It captures entry changes through portable database triggers, processes versioned projection work with stale-worker protection and fingerprint no-ops, runs bounded immediate and scheduled drivers, tracks trustworthy coverage epochs, exposes owner-authorized bounded work list/retry APIs and client methods, and drains existing internal cleanup records.

Activation remains caller-independent and fail-closed: it requires explicit writersDrained: true confirmation and does not add an activation UI, API, or automatic caller. The change preserves V1 compatibility and leaves logged-out query counts unchanged.

This is the Sequence 1 child of #2324 in the Media Library stack rooted at #2218. Keep it draft while the lower stack layers and this layer receive review; do not merge it independently.

Maintainer-approved project work from Matt’s handoff; no separate Discussion applies.

Closes: N/A — stacked child of #2218 through #2324

Type of change

  • Bug fix
  • Feature (requires maintainer-approved Discussion)
  • Refactor (no behavior change)
  • Translation
  • Documentation
  • Performance improvement
  • Tests
  • Chore (dependencies, CI, tooling)

Checklist

  • I have read CONTRIBUTING.md
  • pnpm typecheck passes
  • pnpm lint passes
  • pnpm test passes (or targeted tests for my change)
  • pnpm format has been run
  • I have added/updated tests for my changes (if applicable)
  • User-visible strings in the admin UI are wrapped for translation (if applicable). Do not include messages.po changes except in translation PRs — N/A; no admin UI is added.
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion: N/A — the Media Library project was maintainer-approved in Matt’s handoff and is tracked by feat(admin): media library project #2218.

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: OpenAI Codex (GPT-5.6 Terra)

Screenshots / test output

No visual changes.

Final local verification before opening this draft:

  • Media Usage: 418 passed, 1 skipped
  • Core: 5,243 passed, 4 skipped
  • Migrations: 101 passed
  • OpenAPI: 36 passed
  • Cost/query-plan tests: 29 passed, 1 skipped
  • Cloudflare: 238 passed
  • Workerd: 79 passed
  • Typecheck, lint, formatting, build, and Changesets passed
  • SQLite logged-out query counts and SQL matched committed snapshots exactly
  • D1 routes returned 200 with no query increases; the warmed isolate prevented an exact cold-start comparison
  • PostgreSQL was unavailable because PG_CONNECTION_STRING was unset

Try this PR

Open a fresh playground →

A full working EmDash site, deployed from this branch. Each visit gets its own session-scoped sandbox: no login needed and no shared state. Try the admin, edit content, hit the public site.

Tracks feature/media-usage-sequence-1. Updated automatically when the playground redeploys.

@changeset-bot

changeset-bot Bot commented Aug 9, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 2203f92

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 17 packages
Name Type
emdash Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd Patch
@emdash-cms/plugin-mcp-smoke Patch
@emdash-cms/fixture-perf-site Patch
@emdash-cms/perf-demo-site Patch
@emdash-cms/cache-demo-site Patch
@emdash-cms/do-demo-site Patch
@emdash-cms/do-solo-demo-site Patch
@emdash-cms/admin Patch
@emdash-cms/auth Patch
@emdash-cms/blocks Patch
@emdash-cms/gutenberg-to-portable-text Patch
@emdash-cms/x402 Patch
create-emdash Patch
@emdash-cms/auth-atproto Patch
@emdash-cms/plugin-embeds Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 9, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-playground 2203f92 Aug 11 2026, 04:49 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 9, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-cache 2203f92 Aug 11 2026, 04:49 PM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 9, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
emdash-demo-do 2203f92 Aug 11 2026, 04:50 PM

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

Scope check

This PR changes 12,598 lines across 96 files. Large PRs are harder to review and more likely to be closed without review.

If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs.

See CONTRIBUTING.md for contribution guidelines.

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 9, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Updated (UTC)
✅ Deployment successful!
View logs
docs 2203f92 Aug 11 2026, 04:55 PM

@github-actions

github-actions Bot commented Aug 9, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA ✍️ ✅
Posted by the CLA Assistant Lite bot.

@pkg-pr-new

pkg-pr-new Bot commented Aug 9, 2026

Copy link
Copy Markdown

Open in StackBlitz

@emdash-cms/admin

npm i https://pkg.pr.new/@emdash-cms/admin@2394

@emdash-cms/auth

npm i https://pkg.pr.new/@emdash-cms/auth@2394

@emdash-cms/auth-atproto

npm i https://pkg.pr.new/@emdash-cms/auth-atproto@2394

@emdash-cms/blocks

npm i https://pkg.pr.new/@emdash-cms/blocks@2394

@emdash-cms/cloudflare

npm i https://pkg.pr.new/@emdash-cms/cloudflare@2394

@emdash-cms/contentful-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/contentful-to-portable-text@2394

emdash

npm i https://pkg.pr.new/emdash@2394

create-emdash

npm i https://pkg.pr.new/create-emdash@2394

@emdash-cms/gutenberg-to-portable-text

npm i https://pkg.pr.new/@emdash-cms/gutenberg-to-portable-text@2394

@emdash-cms/plugin-cli

npm i https://pkg.pr.new/@emdash-cms/plugin-cli@2394

@emdash-cms/plugin-types

npm i https://pkg.pr.new/@emdash-cms/plugin-types@2394

@emdash-cms/registry-client

npm i https://pkg.pr.new/@emdash-cms/registry-client@2394

@emdash-cms/registry-lexicons

npm i https://pkg.pr.new/@emdash-cms/registry-lexicons@2394

@emdash-cms/registry-verification

npm i https://pkg.pr.new/@emdash-cms/registry-verification@2394

@emdash-cms/sandbox-workerd

npm i https://pkg.pr.new/@emdash-cms/sandbox-workerd@2394

@emdash-cms/x402

npm i https://pkg.pr.new/@emdash-cms/x402@2394

@emdash-cms/plugin-ai-moderation

npm i https://pkg.pr.new/@emdash-cms/plugin-ai-moderation@2394

@emdash-cms/plugin-atproto

npm i https://pkg.pr.new/@emdash-cms/plugin-atproto@2394

@emdash-cms/plugin-audit-log

npm i https://pkg.pr.new/@emdash-cms/plugin-audit-log@2394

@emdash-cms/plugin-color

npm i https://pkg.pr.new/@emdash-cms/plugin-color@2394

@emdash-cms/plugin-embeds

npm i https://pkg.pr.new/@emdash-cms/plugin-embeds@2394

@emdash-cms/plugin-field-kit

npm i https://pkg.pr.new/@emdash-cms/plugin-field-kit@2394

@emdash-cms/plugin-forms

npm i https://pkg.pr.new/@emdash-cms/plugin-forms@2394

@emdash-cms/plugin-webhook-notifier

npm i https://pkg.pr.new/@emdash-cms/plugin-webhook-notifier@2394

commit: 2203f92

@khoinguyenpham04

Copy link
Copy Markdown
Collaborator Author

recheck

@khoinguyenpham04
khoinguyenpham04 force-pushed the feature/media-usage-sequence-1 branch 2 times, most recently from 04b4c8b to fbd6e20 Compare August 10, 2026 17:43
@khoinguyenpham04
khoinguyenpham04 marked this pull request as ready for review August 10, 2026 17:55
@khoinguyenpham04 khoinguyenpham04 added the bot:review Trigger an emdashbot code review on this PR label Aug 10, 2026

@emdashbot emdashbot Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR builds durable, bounded incremental Media Usage indexing on top of Sequence 0: per-collection database triggers write versioned projection work, a lease-based processor drains that work, a fail-closed activation fence blocks writers during activation, and admin operator APIs plus client methods expose bounded work list/retry. It also threads a beforeContentWrite guard through plugin sandbox paths. The architecture fits EmDash: triggers keep the projection fresh without polling, handlers return the standard ApiResult envelope, admin routes check schema:manage + the admin scope, SQL identifiers are validated before reaching dynamic DDL, and the new middleware only fires on admin/state-changing paths so logged-out query counts are unaffected.

I read the full diff (~383 KB), the new core files under packages/core/src/media/usage/, the repository/handler/route layers, the plugin sandbox changes, the migration, and the tests. The code is careful about cross-dialect timestamps, trigger identity verification, generation leases, and stale-worker fencing. Most AGENTS.md conventions are respected.

I found one logic issue that should be fixed before merge: in packages/core/src/schema/registry.ts the field-mutation methods use invalidateContentMediaUsageSchemaChange() as a boolean probe before the transaction, but that function is not read-only—when incremental capture is active it immediately mutates _emdash_media_usage_index_status. This causes the collection’s coverage epoch to be bumped twice (or three times on error) for a single schema change, and it mutates coverage state even when the transaction later rolls back. Replace the pre-transaction call with a read-only activation check and call the invalidator only after the transaction succeeds or after a rolled-back mutation that actually modified schema. The same pattern repeats in addField, updateField, and deleteField.

That issue aside, the implementation is solid and well-tested.

.executeTakeFirst();

const sortOrder = input.sortOrder ?? (maxSort?.max ?? -1) + 1;
const activeCoverageInvalidated = await invalidateContentMediaUsageSchemaChange(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[needs fixing] activeCoverageInvalidated is set by calling invalidateContentMediaUsageSchemaChange(this.db, collectionSlug), which is not a read-only probe. When incremental capture is active it immediately runs UPDATE _emdash_media_usage_index_status … change_epoch = change_epoch + 1, status = 'stale' … (see src/media/usage/content-refresh.ts). Because the same function is called again after the transaction succeeds, a single field add/update/delete bumps the collection’s coverage epoch twice; if the transaction fails after schemaMutated = true, the catch block calls it a third time. It also mutates coverage state even when the schema change ultimately rolls back.

Use a read-only check (e.g., reading _emdash_media_usage_activation.state) to decide which post-transaction path to take, and only call the mutating invalidator after the transaction commits or after a mutation that actually changed schema:

// before the transaction
const incrementalCaptureActive = await isIncrementalCaptureActive(this.db);

// after the transaction succeeds
if (incrementalCaptureActive) {
	await invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);
} else {
	await markContentMediaUsageCollectionStaleSafely(
		this.db,
		collectionSlug,
		"CONTENT_USAGE_STALE",
	);
}

// in the catch block
if (schemaMutated && incrementalCaptureActive) {
	await invalidateContentMediaUsageSchemaChange(this.db, collectionSlug);
}

The same duplicated pre-transaction invalidation appears in updateField (line ~928) and deleteField (line ~1083) and should be corrected the same way.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ll decline this comment because the two updates are intentional safety checks. They prevent D1 from incorrectly reporting complete coverage during or after a failed schema change.

@emdashbot emdashbot Bot removed the bot:review Trigger an emdashbot code review on this PR label Aug 10, 2026
@github-actions github-actions Bot added the review/awaiting-author Reviewed; waiting on the author to respond label Aug 10, 2026
Comment thread docs/src/content/docs/guides/media-library.mdx Outdated
@ascorbic

Copy link
Copy Markdown
Collaborator

Aside from the docs comment, this is gtg

@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/awaiting-author Reviewed; waiting on the author to respond labels Aug 11, 2026
@github-actions github-actions Bot added review/approved Approved; no new commits since and removed review/needs-rereview Author pushed changes since the last review labels Aug 11, 2026
@khoinguyenpham04
khoinguyenpham04 force-pushed the feature/media-usage-sequence-1 branch from 1a6aa32 to 86f40db Compare August 11, 2026 13:09
@github-actions github-actions Bot added review/needs-rereview Author pushed changes since the last review and removed review/approved Approved; no new commits since labels Aug 11, 2026
@khoinguyenpham04
khoinguyenpham04 force-pushed the feature/media-usage-sequence-1 branch from 86f40db to 9d6ecda Compare August 11, 2026 14:55
@khoinguyenpham04
khoinguyenpham04 force-pushed the feature/media-usage-sequence-1 branch from 9d6ecda to 2203f92 Compare August 11, 2026 16:43
@khoinguyenpham04
khoinguyenpham04 merged commit 170c966 into main Aug 11, 2026
62 of 89 checks passed
@khoinguyenpham04
khoinguyenpham04 deleted the feature/media-usage-sequence-1 branch August 11, 2026 18:09
@emdashbot emdashbot Bot mentioned this pull request Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants