Skip to content

feat: upgrade to Mercure 1.0 - #2611

Merged
dunglas merged 17 commits into
mainfrom
mercure-1.0-alpha3
Sep 30, 2026
Merged

dunglas merged 17 commits into
mainfrom
mercure-1.0-alpha3

Conversation

@dunglas

@dunglas dunglas commented Aug 17, 2026 •

Copy link
Copy Markdown
Member

Upgrades github.com/dunglas/mercure and github.com/dunglas/mercure/caddy from v0.24.2 to v1.0.2, and adapts the integration to the protocol changes of draft-dunglas-mercure-08.

mercure_publish()

The hub validates updates before dispatching them, so the function now distinguishes the caller's mistakes from a failed publication:

  • a ValueError carrying the reason for a topic addressing the reserved /.well-known/mercure namespace or equal to *, an $id starting with # or equal to earliest, a $type equal to mercure, control characters, invalid UTF-8 $data, or no topic at all;
  • a RuntimeException with the message of the hub when the dispatch fails.

The negative values of $retry are also rejected, as the protocol only allows digits in that field.

An enum in frankenphp.h replaces the status codes go_mercure_publish() returned as bare integers. On the way, an array of topics that cannot be converted no longer reports No Mercure hub configured.

Hot reloading

The subscribe query parameter of the protocol is match, so $_SERVER['FRANKENPHP_HOT_RELOAD'] advertises /.well-known/mercure?match=<topic>. The topic parameter it replaces is only honored by a hub built with the deprecated_topic tag, which FrankenPHP doesn't set.

Hub configuration

Setting publisher_jwt or subscriber_jwt without protocol_version_compatibility is a configuration error now, because that mode also drops the required exp, the audience check, the at+jwt check and the issuer check. The keys are bound to a trusted issuer instead:

mercure {
	issuer {$MERCURE_TRUSTED_ISSUERS:https://localhost} {
		publisher {
			jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG}
		}
		subscriber {
			jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG}
		}
	}
}

php-server --mercure and the sample Caddyfile follow. MERCURE_TRUSTED_ISSUERS accepts several identifiers separated by commas or whitespace, all bound to the same keys.

FrankenPHP is built with the deprecated_topic and deprecated_claim tags of the hub, like the official Mercure binaries, so protocol_version_compatibility 8 accepts the 0.x tokens and topic= selectors during a migration. Static builds get the tags through static-php-cli: crazywhalecc/static-php-cli#1240.

Docs

docs/mercure.md and the Laravel Octane section of docs/laravel.md describe the 1.0 configuration. Octane writes each entry of its mercure array as a Caddyfile line, so the issuer block is passed as a multi-line value (verified with caddy adapt and a live hub). The Symfony example targets symfony/mercure 0.8, which ships ProtocolVersion::V1.

The translations of docs/mercure.md and docs/laravel.md still describe the 0.x configuration.

The other Go dependencies are updated on the way.

@alexandre-daubois alexandre-daubois left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I know it's a draft so feel free close anything that was already planned to be fixed 🙂

Comment thread docs/mercure.md Outdated
Comment thread mercure.go Outdated
Comment thread caddy/mercure.go Outdated
Comment thread mercure.go Outdated
Comment thread frankenphp.c Outdated
Comment thread mercure_test.go Outdated
Comment thread caddy/hotreload.go Outdated
Comment thread docs/mercure.md Outdated
Comment thread frankenphp.c Outdated
@dunglas dunglas changed the title feat: upgrade to Mercure 1.0 alpha 3 feat: upgrade to Mercure 1.0 Sep 17, 2026
@dunglas
dunglas force-pushed the mercure-1.0-alpha3 branch 2 times, most recently from 7fb6208 to 6c8e74e Compare September 17, 2026 06:41
@dunglas
dunglas marked this pull request as ready for review September 17, 2026 06:44

@alexandre-daubois alexandre-daubois left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Congrats on the 1.0 release! 🎉

Comment thread frankenphp.c
Comment thread caddy/mercure.go Outdated
Comment thread frankenphp.stub.php
Comment thread caddy/frankenphp/Caddyfile
Comment thread docs/mercure.md
@henderkes

Copy link
Copy Markdown
Contributor

There's a small issue here @dunglas - rebuilds from source currently already lead to Mercure 1.0. I just updated my dev environment to 8.6-rc2 and was met with some fields not being recognised anymore until I change the compatibility level to 8.

@dunglas

dunglas commented Sep 24, 2026

Copy link
Copy Markdown
Member Author

Which ones?

@henderkes

Copy link
Copy Markdown
Contributor

Jwt subscriber and publisher token iirc. I can check again tomorrow. Startup errored out saying they live under jwt now and in order to keep working as they used to, compatibility level has to be set to 8.

@dunglas

dunglas commented Sep 24, 2026

Copy link
Copy Markdown
Member Author

Yes, it's intended.

@henderkes

Copy link
Copy Markdown
Contributor

Yes, that's not the issue - the issue is that duo to Go automatically updating, building FrankenPHP right now already pulls in Mercure v1.0.0, without a new release. So right now php 8.2-8.5 have frankenphp-1.12.7 with Mercure 0.24.0, php 8.6 (rc released yesterday) has frankenphp-1.12.7 with Mercure 1.0.0.

Publish-side validation is part of the protocol now: report what
Update.Validate() rejects (reserved topics and event types, forbidden
event IDs, invalid UTF-8 data) as a ValueError carrying the reason, and
the dispatch failures as a RuntimeException.

The subscribe query parameter becomes "match", so hot reloading advertises
its topic with it. The publisher_jwt and subscriber_jwt directives now
require the compatibility mode, which drops the audience, expiration and
issuer checks: bind the keys to a trusted issuer instead.
The publisher_jwt and subscriber_jwt directives only work in the compatibility
mode of the hub, which FrankenPHP is built without. Octane writes every entry
of the mercure array as a Caddyfile line, so the issuer block is passed as a
multi-line value.
Like the official Mercure binaries and images, ship the deprecated_topic and
deprecated_claim build tags so protocol_version_compatibility 8 can accept 0.x
tokens and topic selectors during a migration. The hub stays in modern mode
unless the directive is set.

Static builds go through static-php-cli, which sets the tags itself and needs
a matching change.
MERCURE_TRUSTED_ISSUERS is a list separated by commas or whitespace, each
identifier bound to the same publisher and subscriber keys.
The caddy module only takes what Mercure 1.0.0 requires: a broader bump
outruns the versions Caddy 2.11.4 compiles against (automemlimit, cel-go).
The hub validates the update in Publish(), so the extra Validate() call is
dropped and its error is classified with errors.Is against the exported
sentinels, then raised through zend_argument_value_error() with the
position of the argument at fault. Non-string topics are rejected in C
with the standard TypeError wording instead of a Go type name.

Tests cover the missing hub, a failing transport, the invalid id and the
non-string topic cases.
symfony/mercure 0.8 requires the sub and client_id claims of RFC 9068 and
the Hub must be told the protocol version to write the 1.0 cookie.
The protocol requires the hub to assign an ID to every update, so an empty
one is a transport bug rather than a case to handle.
golang.org/x/net v0.59.0 deprecates http2.Transport in favor of
http.Transport with Protocols, which staticcheck now flags.
The security release scopes pooled transports by hub name and allows a
single unnamed hub per configuration, so the second test hub gets a name.
automemlimit and cel-go stay at the versions Caddy 2.11.4 compiles against.
Copilot stopped work on behalf of github-stag2 due to an error September 30, 2026 09:46
@dunglas
dunglas merged commit bee677e into main Sep 30, 2026
42 of 43 checks passed
@dunglas
dunglas deleted the mercure-1.0-alpha3 branch September 30, 2026 13:59
GHenry21 added a commit to GHenry21/opensagra that referenced this pull request Oct 1, 2026
…e solo HTTPS

Aggiunta una sezione dedicata in Fase 4 con causa esatta (php/frankenphp#2611),
cosa e' cambiato in config/mercure.php e nei tre Caddyfile, il secondo bug
trovato dal vivo (hardening "un hub senza nome" di Mercure 1.0.2) e la
conseguente semplificazione a solo HTTPS. Aggiornate anche le righe HTTPS e
Realtime della tabella di contesto, che descrivevano ancora il vecchio motivo
(QZ Tray) per tenere HTTP in parallelo - QZ Tray e' stato rimosso in Fase 4,
quel motivo non c'e' piu'.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants