Closes the testing-strategy row of PLAN.md §13 v0.2.0 and parks the
two remaining items with rationales.
Shipped:
- framework/qml/tests/{CMakeLists.txt, main.cpp, tst_smoke.qml}
Qt Quick Test scaffold: QUICK_TEST_MAIN bootstrap + one smoke test
proving the harness loads. New tests land as tst_<feature>.qml in
the same dir; qmltestrunner auto-discovers them. Built only when
-DBUILD_TESTING=ON (production AppImages stay clean).
- skeleton + example/todo Makefiles: `make qmltest` target invokes
the configure → build → ctest dance. `make quality` now depends
on qmltest.
- .gitea/workflows/ci.yml: `QML unit tests` step after qmllint in
the Quality job. Out-of-tree build dir (build-tests) so the
CTest run doesn't pollute the cached release build.
Verified locally: configure + build + ctest pass, both smoke
assertions pass, runs in 0.5s.
Closed in PLAN.md §13 v0.2.0 with rationale (no code change):
- Build-time Symfony cache warmup → moved to v0.3.0. The obvious
approach (cache:warmup at build, copy at first launch) doesn't
save any time because Symfony bakes absolute kernel.project_dir
into the compiled cache, and the AppImage's FUSE mount path
changes every launch — every cached path is stale on launch N+1.
Doing it properly requires virtualising getProjectDir(), symlink
fix-up, multi-app namespacing — its own minor's worth of design.
- ReactiveObject cursor pagination → closed N/A. ReactiveObject
already has pending / invoke() / Idempotency-Key correlation /
version-gap detection at parity with ReactiveListModel; the only
feature it lacks is *pagination*, which is meaningless for a
single-entity model.
That fully closes the v0.2.0 plan as documented. Remaining v0.2.0
items in PLAN.md §13 are the audit-ends already shipped earlier in
the cycle (interfaces / BridgeOp / BridgeBundleInfo / Maker DRY /
--with-dto / port negotiation / pre-migration backup / bridge:export
/ periodic auto-update / native-dialogs doc / event maker /
read-model maker / qmltestrunner) plus the two parked items
documented above. Ready to tag when the user gives the word.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Both items naturally cluster with the v0.9.0 cross-platform packaging
milestone:
- Multi-arch builds share runner + cert prerequisites with the macOS /
Windows ports already at v0.9.0; doing them as one operational push
is cheaper than fanning out across minors.
- Composer create-project is the "how does a new user get the
framework" PHP-side channel — settling it alongside the OS-installer
paths means all the entry points stabilise together for v1.0.0.
v0.3.0 keeps i18n + persistent log files (both standalone work that
doesn't need v0.9.0's operational lift).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The audit's substantive items shipped in chunks 1–4. Two remaining
loose ends inspected and parked:
- Generated controller findOr404 boilerplate. MapEntity changes the
404 response shape away from problem+json unless framework-level
RFC 7807 error config is updated; a private helper is net-zero
on lines. Parking until either (a) skeleton-level RFC 7807 error
wiring, or (b) --with-dto flipping to default-on and the legacy
template's polish becoming irrelevant.
- ModelPublisher::extractId reflection branch. Looks dead because
every maker-output entity has getId(), but it remains a safety net
for hand-written entities that don't. Keeping.
This commit ships:
- BridgeOpTest — locks the enum case values against accidental
rename. Every case value is a documented wire-format token QML
clients hardcode, so renaming a `value` is a wire-protocol break
and this fails the build before it ships.
- PLAN.md §13 v0.2.0 status block with what's shipped on dev
(interfaces / BridgeOp / BridgeBundleInfo / Maker DRY / --with-dto)
and what's still open (findOr404 polish, --with-dto default flip).
Test count 23 → 24, all passing.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The previous commit (0cceefc) staged the audit-driven fixes under a
hypothetical v0.1.3 since v0.1.2 hadn't tagged yet. Tagging cadence
decision: ship them all as v0.1.2 — there's no point spending a tag
on the clean-shutdown fix alone when v0.1.3 was already lined up
behind it. PLAN.md §13 + CHANGELOG.md collapse v0.1.2 + v0.1.3 into
the single v0.1.2 entry, dated 2026-05-03; orphaned [0.1.3] link
reference at the foot of CHANGELOG removed.
No code changes — the four fixes from 0cceefc + f132c3c stand as-is.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Three bugs surfaced by the post-v0.1.2 architecture audit:
- bridge.qml_path is now actually configurable. BridgeBundle::configure
defines the qml_path scalar node (default ../qml/); loadExtension
exposes it as the bridge.qml_path container parameter; services.yaml
binds it into BridgeResourceMaker + BridgeWindowMaker. Apps override
with `config/packages/bridge.yaml`. The existing maker docstrings
claimed this worked already — they lied; now they don't.
- SessionAuthenticator implements AuthenticationEntryPointInterface and
routes the no-token entry-point path through the same problem+json
helper as onAuthenticationFailure, so QML's RestClient sees one error
shape regardless of which firewall path was taken. Test added.
- CorrelationKeyListener::onTerminate guards on isMainRequest() now,
matching onRequest's existing guard. No user-visible impact in
worker mode (no sub-requests emitted), but the asymmetry was a
defensive bug that would corrupt optimistic-update reconciliation.
PLAN.md §13 gains a v0.1.3 section + folds the audit's API-surface
items (PublisherInterface / ModelPublisherInterface / BridgeOp enum /
maker DRY / DTO-shaped scaffold) into v0.2.0. CHANGELOG.md mirrors.
PHPStan + cs-fixer + PHPUnit (17/17) + maker snapshot tests all green.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The v0.1.2 section had been prepended above v0.1.1 instead of inserted
after it; reads strictly chronologically now (v0.1.0 → v0.1.1 → v0.1.2
→ v0.2.0 → …).
Also brought v0.1.1's heading and bullet list up to current reality:
- heading: "ready to tag" → "shipped 2026-05-03" (it was tagged
earlier today)
- added the cache-wipe-on-bundled-launch fix, which actually landed
in v0.1.1 (rotated into the tag) but was missing from PLAN.md's
summary (CHANGELOG already had it)
Top-of-file status line: "v0.1.1 ready to tag" → "v0.1.0 + v0.1.1
shipped 2026-05-03; v0.1.2 in progress on dev".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Symptom (user report on v0.1.1):
QProcess: Destroyed while process ("/tmp/.mount_Todo-xbNoHHL/usr/bin/frankenphp") is still running.
…and the frankenphp child + its PHP workers were left orphaned after
the host exited.
Cause: teardownChild() was only called from ~BackendConnection. By
the time that destructor runs, app.exec() has already returned,
QQmlApplicationEngine is mid-destruction, and Qt's event loop is
half-torn-down. waitForFinished() doesn't reliably reap the child in
that window — QProcess gets destroyed by the QObject parent-chain
cleanup before the kernel reports the child as exited.
Fix: in BackendConnection's constructor, connect
QCoreApplication::aboutToQuit → teardownChild. aboutToQuit fires
while the event loop is still active and BEFORE main() starts
unwinding the stack, so SIGTERM + waitForFinished can do their job
properly. The destructor's teardownChild call stays as belt-and-
suspenders (no-op once aboutToQuit has already cleaned up — the
function is idempotent via the m_child = nullptr at its end).
The connect happens unconditionally in the constructor (not just for
bundled mode) because m_child is also nullptr in dev mode and
teardownChild handles that with its leading `if (!m_child) return;`.
Regression guard: examples/todo/tests/bundled-supervisor.sh gains a
"graceful shutdown" step:
- Snapshots the host's child PIDs before SIGTERM
- SIGTERMs the host, waits up to 3s for clean exit
- Greps the host log for "QProcess: Destroyed while" — fail if found
- Iterates the snapshotted PIDs, fails on any frankenphp orphan still alive
Verified locally: real AppImage + the integration test both clean up
without Qt warnings or orphan processes.
PLAN.md: new v0.1.2 section above v0.1.1, this is its first entry.
CHANGELOG.md: [0.1.2] — TBD section with the same Fixed entry.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CHANGELOG.md: new [0.1.1] section with Fixed (HealthController
deep-load, Caddyfile fmt) + Added (bundled-mode supervisor test,
skeleton AppImage parity) + a Notes line acknowledging the
port-collision bug deferred to v0.2.0. Date stays TBD until tag push.
Compare/tag link refs updated.
PLAN.md: v0.1.1 section flipped from "open follow-ups" to "ready to
tag" with each item describing what shipped (handy for the release
notes pass). v0.2.0 section gains an explicit "Bundled-mode port
negotiation" entry under Operations — the port-collision bug
surfaced during v0.1.1 prep, but the fix touches every consumer that
hardcodes 8765 (perfsmoke, the new bundled-supervisor test) so it's
wider than v0.1.x scope. Status line at the head of the file bumped
to "v0.1.1 ready to tag".
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Same logic as the macOS/Windows + telemetry moves: alternate
distribution channels are operational work (Flatpak manifest +
Flathub PR review; snapcraft.yaml + Snap Store listing) that fits
the cross-platform packaging milestone, not the v0.3.0 grab-bag.
Tightened the v0.9.0 framing to make this explicit: AppImage is the
only packaged target through v0.2.0, v0.3.0, and the v1.0.0 prep —
all packaging churn concentrated into v0.9.0.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Telemetry was split awkwardly: v0.3.0 had "Sentry + opt-in telemetry"
(build the pipeline) and v1.0.0 had "Telemetry / crash reporting
opt-in plumbing" (settle the API). The cross-platform crash-dump side
is per-OS work — Apple Crash Reporter, Windows WER, Linux core dumps
all differ — so it naturally rides with the v0.9.0 cross-platform
packaging push rather than landing twice.
Single v0.9.0 entry now covers both: PHP-side Sentry + per-platform
crash-dump pipeline, opt-in only, plumbing settled before v1.0.0 even
if no default endpoint ships.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Each cross-platform target carries operational prerequisites that
v0.2.0 isn't ready to absorb (Apple Developer cert + notarisation
pipeline + macOS runner; Authenticode cert + SmartScreen reputation
warm-up + Windows runner). Folding both into the next minor would
either delay v0.2.0 indefinitely or ship a half-done port.
Better: keep Linux AppImage as the only packaged target until the
framework's public API surface settles, then concentrate the
cross-platform push into a single v0.9.0 release-candidate milestone
right before v1.0.0. The §11 *Distribution UX* foot-guns (Gatekeeper,
SmartScreen, AV pre-submissions, file-association docs) ride along
with that milestone.
v0.2.0 stays focused on the smaller deferred items (deferred makers,
ReactiveObject pagination, qmltestrunner, end-to-end UI test,
auto-backup, bridge:export, periodic auto-update, build-time cache
warmup, native-dialog boundary doc) — all things a Linux-only
contributor can deliver without operational blockers.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
§13 was 332 lines of phase-by-phase implementation history (Phase 0
spike → Phase 5 closure). All of that is now redundant with
CHANGELOG.md (per-version summary) and `git log` (per-commit detail);
keeping it in PLAN.md was duplication that would only rot.
Replaced with a Versions section organised around SemVer:
- v0.1.0 — shipped 2026-05-03 (links to CHANGELOG + release page).
- v0.1.1 — bugfix follow-ups from v0.1.0 shakedown (perfsmoke gap,
bin/php-qml-init parity with the AppImage fixes that landed in
examples/todo, bundled-mode integration test, Caddyfile fmt).
- v0.2.0 — minor features: macOS/Windows packaging (was Phase 4b/4c),
deferred makers (event/read-model), ReactiveObject pagination,
qmltestrunner + end-to-end UI test, pre-migration auto-backup,
bridge:export, periodic auto-update check, build-time cache warmup,
native-dialogs boundary doc.
- v0.3.0 — bigger pieces: i18n bridge, persistent log files +
rotation, multi-arch builds, Sentry + opt-in telemetry, Flathub /
Snap, Composer create-project package.
- v1.0.0 — when public API stabilises: auth model, Mercure storage,
AppImage relinkability, telemetry plumbing, security audit,
FrankenPHP-as-library evaluation.
Every deferred item from the original §11/§12/Phase 3-5 deferral
lists got a version target — no orphans.
Top-of-file status line and "Where else to look" pointer added so
readers know docs/ is for how-to-use, CHANGELOG for what-shipped, and
PLAN.md keeps why + what's next.
Net: 836 → 561 lines (33% smaller). §1-12 (architectural rationale)
unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the two release-prep items called out in the Phase 5 closure
paragraph (a3d35a7).
License: LGPL-3.0-or-later. Chosen to align with Qt 6's LGPLv3, which
keeps the AppImage's relinkability obligation (PLAN.md §12) satisfied
and avoids version-mixing friction with upstream Qt. Two files at the
repo root:
- LICENSE — LGPL-3.0 text (the project license).
- LICENSE.GPL — GPL-3.0 text the LGPL-3.0 explicitly incorporates
("This version of the GNU Lesser General Public
License incorporates the terms and conditions of
version 3 of the GNU General Public License…").
framework/php/composer.json: "license": "proprietary" → SPDX
"LGPL-3.0-or-later". CHANGELOG Notes section updated with the actual
license + LICENSE/LICENSE.GPL pointer.
Repo URL: every `gitea.example/<org|you>/php-qml` (and `<org>/<repo>`
in docs/packaging-linux.md) replaced with the real
`src.bundespruefstelle.ch/magdev/php-qml`. Touched README.md,
CHANGELOG.md (compare + tag links), docs/getting-started.md,
docs/packaging-linux.md (build-appimage --update-info example +
latest.json appcast example).
PLAN.md: status line bumped to "v0.1.0 ready to tag — LGPL-3.0-or-later
license shipped, repo URL fixed". Phase 5 closure paragraph rewritten
to record both items resolved (rather than pending).
Only remaining manual edit at tag time: CHANGELOG `[0.1.0] — TBD` →
`[0.1.0] — YYYY-MM-DD` (per Keep-a-Changelog), and the actual
`git tag v0.1.0 && git push --tags` itself, which triggers
.gitea/workflows/release.yml. Per the branching memory, releases land
on main — merge dev → main first.
Verified: `make quality` from framework/skeleton green (16 tests, 45
assertions; PHPStan + cs-fixer clean; maker snapshots match).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
All four planned sub-commits (4c15ac2 → 26a2b37) landed; the unplanned
docs/ rewrite (da04843) followed, lifting long-form material out of
the README into ten topic guides under docs/.
Done-criteria checked:
- bin/php-qml-init scaffolds a runnable app (sub-commit 2).
- DevConsole.qml + childLogLine signal surface child stdout/stderr
with Ctrl+` toggle in skeleton + todo example (sub-commit 1).
- README is end-to-end (60-second tour + links into docs/).
- CHANGELOG v0.1.0 entry records every Phase 0–4a + Phase 5 deliverable.
- `make quality` from framework/skeleton passes (16 tests, 45
assertions; PHPStan clean; cs-fixer clean; qmllint warnings only;
maker snapshots match).
Status line at PLAN.md head bumped to "Phases 0–5 complete; v0.1.0
ready to tag pending LICENSE selection + Gitea host URL substitution".
Two release-prep items remain — both user-driven, neither a framework
regression — captured in the new Phase-5-closure paragraph: choose +
add a LICENSE (composer.json still says "proprietary"), and replace
the `gitea.example/<org>/` placeholder URLs in CHANGELOG / README /
docs once the repo's published location is fixed. Tagging itself
remains the user's call per the release-process memory.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
README.md rewritten to reflect actual onboarding (clone → php-qml-init
→ make dev / make appimage) instead of the planning-stage placeholder.
Phase status checklist now reflects 0–4a green and 5 in progress.
CHANGELOG.md created at repo root following Keep-a-Changelog
conventions, with a v0.1.0 entry that summarises Phases 0–4a plus the
Phase 5 polish work (DevConsole, php-qml-init, editor configs,
hot-reload docs). Date is TBD; tagging is the user's call.
PLAN.md gains a Status banner so it's obvious at a glance which phase
the implementation tracks against the design.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Phase 5 closes out the remaining DX seams from PLAN.md §8: child-output
capture with an optional DevConsole.qml, a single-file `bin/php-qml-init`
bash script for scaffolding fresh apps (chosen over a Composer template
since the project mixes PHP and CMake/Qt — no clean Composer fit), the
hot-reload story documented and IDE configs shipped with the skeleton,
then a release-readiness pass (README, CHANGELOG.md) culminating in a
plausible v0.1.0 milestone — tagging stays user-driven per the
release-process feedback rule.
4 sub-commits. macOS / Windows packaging stays deferred to 4b / 4c.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
examples/todo/tests/perfsmoke.sh asserts the PLAN.md §11 budgets
against the built AppImage:
- Bundle size ≤ 200 MB (hard cap; ≤ 120 MB target)
- Cold start ≤ 2000 ms from launch to first /healthz 200
- Idle RSS (host + descendants in the process group) ≤ 200 MB after
a 2 s settle.
Each budget is overridable via env (PERF_COLD_START_MS etc.) for slow
shared CI runners; defaults are the strict numbers from the plan. Runs
the AppImage under xvfb-run when DISPLAY is unset; falls back to
QT_QPA_PLATFORM=offscreen otherwise (the build script already bundles
libqoffscreen.so via EXTRA_PLATFORM_PLUGINS).
Wired into:
- examples/todo/Makefile → `make perf`
- .gitea/workflows/release.yml → runs after AppImage build, before
zsync + upload, with cold-start budget bumped to 4 s for CI.
CI now also installs zsync + xvfb in one step.
examples/todo/README.md gains an "AppImage packaging (Phase 4a)"
section walking through `make appimage`, bundled-mode behaviour, the
auto-update QML hooks (BackendConnection.checkForUpdates() / applyUpdate()),
and `make perf`.
PLAN.md §13 Phase 4 marked **4a closed**. 4b (macOS) and 4c (Windows)
stay stubs until their runners + certs exist.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI was failing on the Install-bundle-dependencies step because
shivammathur/setup-php was installing 8.3 while Symfony 8.x dependencies
declare php >= 8.4. Local composer install worked because the dev box
runs PHP 8.5.5; CI doesn't.
Bumps:
- framework/php/composer.json
- framework/skeleton/symfony/composer.json
- examples/todo/symfony/composer.json
- .gitea/workflows/ci.yml php-version: '8.3' → '8.4'
- .gitea/workflows/release.yml same
- PLAN.md §13 Phase 1 *Detailed scope* PHP minimum row
PHPStan / cs-fixer / PHPUnit stay green locally.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Honest scoping: macOS and Windows packaging carry hard operational
prerequisites (self-hosted runners, Apple Developer + Authenticode certs)
that can't be solved from a Linux dev machine. Phase 4a delivers the full
Linux pipeline now; 4b and 4c wait until those prerequisites land.
4a sub-commits:
1. Bundled-mode startup (auto-detected: no BRIDGE_URL → spawn child,
per-session secret, first-launch migrations into XDG data dir)
2. AppImage recipe (packaging/linux/build-appimage.sh + make appimage)
3. Linux release.yml on v* tags (Gitea Release + SHA256SUMS + appcast)
4. AppImageUpdate + BackendConnection.checkForUpdates()
5. Performance-smoke harness + 4a phase closure
The framework code stays platform-agnostic — only the packaging layer
is per-OS. 4b / 4c entries get filled into PLAN.md when their runners
and certs become available.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a 5-step sub-commit sequence and the done-criteria for Phase 3.
Defaults baked in (subject to user override before sub-commit 1):
- Ship `make:bridge:command` and `make:bridge:window` only; defer
`make:bridge:event` and `make:bridge:read-model` since the todo
app doesn't use them. Phase 3.x can pick them up.
- Multi-window + crash-recover validated via a bridge-integration test
that boots a real FrankenPHP child + offscreen Qt host. qmltestrunner
smoke covers RestClient.qml and AppShell.qml.
- Maker-output snapshot test in CI catches silent generator drift by
diffing fresh maker runs against the checked-in baseline.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
BackendConnection's ConnectionState enum is now Connecting / Online /
Reconnecting / Offline (PLAN.md §5). The probe loop tracks the first
failure since the last Online and transitions to Reconnecting on any
failed probe, then to Offline once the configurable threshold (30 s
default) is exceeded. The Error state is gone; Reconnecting + the
exposed `error` string subsume its UI role.
ReactiveListModel is the headline QML type:
- QAbstractListModel that GETs `baseUrl + source` for an initial JSON
array and then keeps in sync via an internal MercureClient subscribed
to `topic`.
- Role names are derived dynamically from the first row's keys plus an
internal `pending` boolean role used by optimistic mutations.
- Diff application: upsert (insert-or-update), delete, replace; gap
detection via the envelope `version` field with auto re-fetch.
- `invoke(method, path, body, optimistic)` is the optimistic command
primitive. Generates an Idempotency-Key, applies the local diff,
POST/PATCH/DELETEs with that key, and resolves on the matching
Mercure echo (correlation-key matched in ModelPublisher's envelope).
Rolls back and emits commandFailed on 4xx/5xx, commandTimedOut after
10 s without an echo. Phase 4 packaging will surface configuration
for the timeout.
AppShell.qml is the optional convenience root:
- Reads BackendConnection.connectionState.
- Reconnecting → top banner.
- Offline → modal overlay with the error string and a Retry button
(calls BackendConnection.restart()).
- Wraps user content via `default property alias content`.
Apps that want full chrome control can skip AppShell entirely; the
skeleton's Main.qml keeps its own status display for demonstration
and is unaffected.
ReactiveObject (single-entity twin of ReactiveListModel) is intentionally
deferred — same envelope handling, smaller surface; will land in Phase 2
follow-up or Phase 3 alongside the todo example. Cursor pagination is
similarly deferred (the Phase 2 done criterion uses small lists).
Smoke tested: /healthz + /api/ping round-trip cleanly, zero Mercure 401s,
clean shutdown. composer quality stays green (16 tests, 45 assertions).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Makes the time-ordered, distributed-friendly UUIDv7 (via symfony/uid)
the framework's default. Auto-increment integers remain available via
an explicit --int-id flag on the maker.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds a stack-additions table (ORM, dev DB, default ID type, pagination
strategy, Doctrine→Mercure trigger), a 5-step sub-commit sequence, and
the done-criteria for Phase 2. Defaults baked in (subject to user
override before sub-commit 1 starts):
- Doctrine ORM 3.x with DoctrineBundle + DoctrineMigrationsBundle
- SQLite at var/data.sqlite for dev
- Auto-incrementing int IDs by default; the maker takes a UUIDv7 flag
- Cursor-based pagination, default page size 50
- Synchronous postPersist/postUpdate/postRemove subscribers
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Bundle code for php-qml/bridge: BridgeBundle (AbstractBundle, autoloads
config/services.yaml), Publisher (thin wrapper over Mercure HubInterface
that enforces envelope-as-JSON), SessionAuthenticator (bearer-token
custom Symfony authenticator with problem+json failures), and
HealthController (GET /healthz readiness probe).
Composer constraints bumped to Symfony ^8.0 across the board (per user
request); mercure component to ^0.7. PHPUnit 11 suite covers Publisher
publish + private flag and SessionAuthenticator support/auth/failure
paths — 8 tests, 22 assertions, all green.
PLAN.md §13 updated to record the Symfony 8 minimum.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds naming/identifier table, directory layout, eight sub-commits, and
done criteria for Phase 1. Also swaps the planned Taskfile for a plain
Makefile across §8 and §13 — Make is universal and skips a dependency
that openSUSE Tumbleweed doesn't package.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds the spike's layout, flow, hardcoded values, and done criteria so
implementation has a clear target. No code yet.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Architectural design (PLAN.md) and project intro committed before any
implementation begins.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>