Skip to content

feat(release-service): isolate verification egress#1988

Merged
ascorbic merged 5 commits into
feat/delegated-release-servicefrom
feat/delegated-release-service-16-verifier-worker
Jul 13, 2026
Merged

feat(release-service): isolate verification egress#1988
ascorbic merged 5 commits into
feat/delegated-release-servicefrom
feat/delegated-release-service-16-verifier-worker

Conversation

@ascorbic

@ascorbic ascorbic commented Jul 12, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Adds the private release-verifier Worker for W5.1a of the delegated release service. Artifact and provenance retrieval now crosses a typed service-binding RPC boundary, keeping outbound network access out of the public release-service Worker and failing closed when the verifier is unavailable or returns malformed data.

The Worker reuses the registry-verification package's bounded safe-fetch implementation through a new Worker-safe export, with fixed DoH resolution, redirect revalidation, timeout limits, and separate artifact/provenance size limits.

Part of #1908.

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 (not applicable: no admin UI changes)
  • I have added a changeset (if this PR changes a published package)
  • New features link to an approved Discussion: not applicable; implementation is tracked by the maintainer-owned integration PR Delegated release service #1908

AI-generated code disclosure

  • This PR includes AI-generated code — model/tool: OpenCode with GPT-5.6 Sol

Screenshots / test output

Non-visual change. Verified after rebasing onto the current integration branch:

  • pnpm build
  • pnpm typecheck
  • pnpm lint and pnpm lint:json
  • release-service build, typecheck, and 130 tests
  • release-verifier dry-run build, typecheck, and 5 tests
  • registry-verification 93 Node tests and 36 workerd tests
  • generated Wrangler types are current for both Workers

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 feat/delegated-release-service-16-verifier-worker. Updated automatically when the playground redeploys.

@changeset-bot

changeset-bot Bot commented Jul 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 638f9df

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

This PR includes changesets to release 19 packages
Name Type
@emdash-cms/registry-verification Minor
emdash Patch
@emdash-cms/plugin-cli Patch
@emdash-cms/release-verifier Patch
@emdash-cms/cloudflare Patch
@emdash-cms/sandbox-workerd 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 Jul 12, 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 638f9df Jul 13 2026, 06:28 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 12, 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 638f9df Jul 13 2026, 06:29 AM

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 12, 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 638f9df Jul 13 2026, 06:30 AM

@github-actions

Copy link
Copy Markdown
Contributor

Scope check

This PR changes 15,129 lines across 23 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 added size/XL review/needs-review No maintainer or bot review yet cla: signed labels Jul 12, 2026
@ascorbic ascorbic added the bot:review Trigger an emdashbot code review on this PR label Jul 12, 2026
@github-actions

Copy link
Copy Markdown
Contributor

Overlapping PRs

This PR modifies files that are also changed by other open PRs:

This may cause merge conflicts or duplicated work. A maintainer will coordinate.

@ascorbic ascorbic added bot:review Trigger an emdashbot code review on this PR and removed bot:review Trigger an emdashbot code review on this PR labels Jul 12, 2026
@ascorbic
ascorbic force-pushed the feat/delegated-release-service-16-verifier-worker branch from 900b4c9 to c494fc0 Compare July 12, 2026 19:38
@ascorbic ascorbic added bot:review Trigger an emdashbot code review on this PR and removed bot:review Trigger an emdashbot code review on this PR labels Jul 12, 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.

This PR adds the isolated release-verifier Worker and the release-service client adapter, which moves artifact/provenance egress out of the public release-service Worker — exactly the shape described in the W5.1a plan. The change is well-scoped: it introduces a new private app, a registry-verification/fetch Worker-safe entry point, a typed service-binding RPC interface, and matching workerd/Node tests. No EmDash core conventions (Lingui, RTL, D1 queries, content-table locale filtering, etc.) are touched.

I checked the new verifier Worker code, the service-binding client in apps/release-service/src/verifier.ts, the registry-verification packaging changes (fetch-entry.ts, tsdown.config.ts, check-packed-output.mjs), the wrangler configs, generated Wrangler types, and the existing fetch.ts implementation. The code is generally clean and correctly fails closed: malformed RPC results, binding rejections, and validation failures are all normalized to a retryable VerifierUnavailableError.

One real gap and one minor suggestion:

  • Provenance size vs. RPC message limit. PROVENANCE_MAX_BYTES is set to exactly 1024 * 1024, but the generated worker types repeat a 1MiB Workers RPC message limit. The verifier returns provenance bytes wrapped in a VerificationResult<Uint8Array> over a service-binding RPC call, so the envelope adds overhead. A provenance that is legal under the current constant could still fail to serialize across the RPC boundary.
  • Observability of verifier failures. The VerifierUnavailableError catch discards the original RPC/validation error, which will make production "verifier unavailable" incidents hard to diagnose. Preserving the cause on the error object helps logs while keeping client responses unchanged.

I did not run the test suite or build; that claim is unverified from this review.

Comment thread apps/release-verifier/src/index.ts Outdated
import { WorkerEntrypoint } from "cloudflare:workers";

export const ARTIFACT_MAX_BYTES = 384 * 1024;
export const PROVENANCE_MAX_BYTES = 1024 * 1024;

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] PROVENANCE_MAX_BYTES = 1024 * 1024 sets the provenance cap flush against the Workers RPC message limit noted in the generated worker types ("RPC limit for the batch (1MiB)"). The verifier returns provenance bytes over a typed service-binding RPC as { success: true, value: Uint8Array }, so the wrapper object and RPC envelope add bytes beyond the raw payload. A provenance file that is near but under this limit can pass the safe-fetch size check yet fail to serialize across the RPC boundary, producing an opaque transport error instead of a clean VerificationResult.

Leave headroom for the envelope, and ideally express the constant relative to a documented RPC limit rather than a bare literal:

Suggested change
export const PROVENANCE_MAX_BYTES = 1024 * 1024;
export const PROVENANCE_MAX_BYTES = 1024 * 1024 - 1024;

(A named constant like WORKERS_RPC_MESSAGE_LIMIT_BYTES would be even clearer.)

fetchProvenance(url: string): Promise<unknown>;
}

export class VerifierUnavailableError extends Error {

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.

[suggestion] The catch in callVerifier replaces every binding/validation failure with a fresh VerifierUnavailableError, throwing away the original cause. That matches the "fail closed" design, but when this bubbles up in production logs there will be no trace of why the verifier was considered unavailable — RPC transport error, malformed response, version-skewed error code, etc.

Consider stashing the original error as cause:

export class VerifierUnavailableError extends Error {
	readonly retryable = true;

	constructor(cause?: unknown) {
		super("Release verifier is unavailable", { cause });
		this.name = "VerifierUnavailableError";
	}
}

and then rethrowing new VerifierUnavailableError(error). This keeps the client response unchanged while giving operators something actionable in the logs.

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-review No maintainer or bot review yet labels Jul 12, 2026
@ascorbic

ascorbic commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator Author

Addressed both review points in 67ba3c9f:

  • Reduced provenance payloads to 960 KiB, reserving 64 KiB for the VerificationResult RPC envelope under Workers' 1 MiB message cap.
  • Preserved binding and malformed-response diagnostics through VerifierUnavailableError.cause while retaining the generic client-facing message.
  • Added failing-first regression coverage for both behaviors.

Verification after updating to integration head 75498780: full pnpm build, pnpm typecheck, pnpm lint:quick, pnpm lint:json, 208 release-service tests, 5 release-verifier tests, and both app typechecks pass. An independent second-model review found no high or medium issues.

~ Sol 🤖

@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 Jul 13, 2026
@ascorbic ascorbic added bot:review Trigger an emdashbot code review on this PR and removed bot:review Trigger an emdashbot code review on this PR labels Jul 13, 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.

This is a re-review of the isolated verifier Worker PR. The two findings from the previous review are now addressed:

  • apps/release-verifier/src/index.ts now caps provenance bytes at 960 * 1024, leaving headroom below the 1 MiB Workers RPC message limit, and the constant/test comment explains the envelope overhead.
  • apps/release-service/src/verifier.ts now passes the original RPC/validation error as the cause of VerifierUnavailableError, and tests assert the cause is preserved across malformed responses and binding rejections.

The overall approach is sound: moving artifact/provenance egress to a private Worker behind a typed service binding is exactly the shape described in W5.1a, and the new registry-verification/fetch entry point lets the verifier reuse the existing bounded fetch implementation in Worker-safe packaging.

One real issue remains: the release-service RPC adapter validates verifier error results against a hardcoded allowlist of VERIFICATION_ERROR_CODES. A newer verifier that returns a valid { success: false, error: { code, message } } with a code added after this client was deployed is classified as “malformed” and rethrown as a retryable VerifierUnavailableError. That turns a definitive verification failure into a transient, retryable service error and breaks forward compatibility across the service boundary. The RPC boundary already guarantees the object shape; the client should only validate that code and message are strings, not enumerate every possible code.

I did not run builds or tests; the author’s claim that pnpm build, pnpm typecheck, and suite runs passed is unverified from this review.

Comment thread apps/release-service/src/verifier.ts Outdated
Comment on lines +61 to +65
function isVerificationError(value: unknown): value is VerificationError {
return (
isRecord(value) &&
typeof value["code"] === "string" &&
VERIFICATION_ERROR_CODE_SET.has(value["code"]) &&

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] The adapter treats a well-formed { success: false, error: { code, message } } result as malformed whenever the verifier uses an error code not in the client’s hardcoded VERIFICATION_ERROR_CODE_SET. That turns forward-compatible new failure codes (version skew, new policy checks, etc.) into a retryable VerifierUnavailableError, masking a definitive verification failure as transient verifier unavailability and defeating the purpose of a separated service boundary.

The typed service binding already guarantees the result shape, so validate only the shape here, not the specific code.

Suggested change
function isVerificationError(value: unknown): value is VerificationError {
return (
isRecord(value) &&
typeof value["code"] === "string" &&
VERIFICATION_ERROR_CODE_SET.has(value["code"]) &&
function isVerificationError(value: unknown): value is VerificationError {
return isRecord(value) && typeof value["code"] === "string" && typeof value["message"] === "string";
}

Then remove the now-dead VERIFICATION_ERROR_CODE_SET constant (line 8) and the VERIFICATION_ERROR_CODES import (line 5).

it.each([
null,
{ success: true, value: "not bytes" },
{ success: false, error: { code: "NEW_VERSION_CODE", message: "version skew" } },

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] This test case labels a valid version-skew error result as a “malformed RPC result.” A { success: false, error: { code: "NEW_VERSION_CODE", message: "version skew" } } response has exactly the shape the adapter should pass through; it is not malformed and should not become a VerifierUnavailableError.

Move it out of the malformed list and assert that the adapter returns the error result unchanged, instead of normalizing it to a retryable verifier-unavailable error.

@github-actions github-actions Bot added the review/awaiting-author Reviewed; waiting on the author to respond label Jul 13, 2026
@github-actions github-actions Bot removed the review/needs-rereview Author pushed changes since the last review label Jul 13, 2026
@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 Jul 13, 2026
@ascorbic ascorbic added bot:review Trigger an emdashbot code review on this PR and removed bot:review Trigger an emdashbot code review on this PR labels Jul 13, 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.

This PR correctly isolates artifact/provenance egress to a private verifier Worker behind a typed service binding, which is the right shape for W5.1a. The approach reuses the existing bounded fetch implementation via the new @emdash-cms/registry-verification/fetch entry point instead of duplicating it, and the service-binding adapter fails closed on binding or malformed-response errors.

The two prior blocking concerns are resolved:

  • apps/release-verifier/src/index.ts caps provenance at 960 * 1024 bytes, with a test and comment explaining the headroom below the Workers RPC 1 MiB limit.
  • apps/release-service/src/verifier.ts wraps every non-verifier error in VerifierUnavailableError with the original RPC/validation error preserved as cause, and tests assert that preservation.
  • The previous allowlist-of-error-codes problem is gone: the adapter now only validates that { success: false, error: { code, message } } has string code/message, so a newer verifier can return new definitive failure codes without being misclassified as malformed/retryable.

I read the changed files and tests statically; I did not run builds, typecheck, lint, or tests. The author’s claim that those pass is unverified from this review.

One small cleanup remains: apps/release-service/package.json adds a dependency on @emdash-cms/registry-verification that the release-service code never imports. The verifier app uses it, and the service-binding types are generated from the verifier’s Wrangler config, so it appears to be dead weight in the release-service package.

},
"dependencies": {
"@emdash-cms/registry-verification": "workspace:*",
"jose": "^6.1.3"

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.

[suggestion] The added @emdash-cms/registry-verification dependency is not imported anywhere in apps/release-service (the verifier app imports it, and the service-binding types are generated from ../release-verifier/wrangler.jsonc). It adds unused weight to the lockfile and is out of scope for this package.

Suggested change
"jose": "^6.1.3"
"dependencies": {
"jose": "^6.1.3"
}

@github-actions github-actions Bot added review/awaiting-author Reviewed; waiting on the author to respond and removed review/needs-rereview Author pushed changes since the last review labels Jul 13, 2026
@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 Jul 13, 2026
@ascorbic ascorbic added bot:review Trigger an emdashbot code review on this PR and removed bot:review Trigger an emdashbot code review on this PR labels Jul 13, 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.

Re-review. The isolation approach remains correct: outbound artifact/provenance fetches now happen only in the private release-verifier Worker, accessed from release-service through a typed service binding, and failures are normalized to a retryable VerifierUnavailableError while definitive verifier failures pass through unchanged.

The one cleanup from the previous review is done: apps/release-service/package.json no longer declares an unused runtime dependency on @emdash-cms/registry-verification; only the new release-verifier app depends on the package.

I read the changed files and tests statically; I did not run builds, typecheck, lint, or tests. No new correctness, security, or convention issues stand out in this revision.

@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 Jul 13, 2026
@ascorbic
ascorbic merged commit ca62dd9 into feat/delegated-release-service Jul 13, 2026
10 checks passed
@ascorbic
ascorbic deleted the feat/delegated-release-service-16-verifier-worker branch July 13, 2026 06:38
@ascorbic ascorbic mentioned this pull request Jul 13, 2026
63 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bot:review Trigger an emdashbot code review on this PR cla: signed overlap review/approved Approved; no new commits since size/XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant