The three data modules' docs were in Italian, so the synchronization contract
added in the previous commit went in as Italian too, to match its file. English
is the project's language for docs, comments and READMEs alike, and a file half
in each is worse than either — so all three are translated, not just the new
section.
Content is otherwise unchanged, except the "synchronizations run on
commit/rollback" line in the two backend READMEs, which was vague before and is
now accurate about which hook sees the session/connection still bound, pointing
at flash-ext-data-core's README for the full contract.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
beforeCommit(boolean) has been part of the TxSynchronization API from the
start and was invoked by nothing, in either manager — anyone implementing it
got silence, the same failure class as the dropped afterCommit callbacks in
the previous commit. Left out of that one because fixing it is a design
decision rather than a restored behaviour; this is that decision, written
down.
It now runs immediately before the real commit, with the transaction still
active and its session/connection still bound, which is the whole reason to
have a hook on this side of the commit: it can still write through the same
resource and land in the same atomic unit. Skipped when the transaction is
already rollback-only, since there is no commit to precede.
Throwing from it vetoes the commit: the transaction rolls back, the surviving
callbacks hear ROLLED_BACK, and the exception propagates. Without that, a hook
running before the commit would be strictly less useful than one running
after. A commit that fails on its own now takes the same path instead of
completing silently with no callback at all, and a rollback that also fails is
attached as a suppressed exception rather than replacing the one that explains
the failure.
Documented on the interface itself and in flash-ext-data-core/docs/README.md:
which hook sits on which side of the commit, what each may still touch, what
throwing does, and the per-transaction scoping rule from the previous commit.
Tests: 6 more (3 per manager) — runs inside the transaction with the resource
still bound, receives the read-only flag, skipped on a rollback-only
transaction, and vetoes the commit when it throws. 37 across the two managers
now, all green, reactor verify passes the 80% Jacoco gate.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two defects in the same mechanism, both silent.
1. HibernateTxManager#commit fired synchronizations *after* its finally block,
where cleanupIfIdle() had already called ResourceRegistry.cleanup() and
removed the ThreadLocal list holding them. fireSynchronizations() then read
a freshly initialized empty list and did nothing. No afterCommit callback
had ever run on the Hibernate path: no exception, no log, just silence.
rollback() twenty lines below had the order right, which is what makes this
an ordering slip rather than a design choice.
Found in production, from the far end: an admin write landed in Postgres
while the in-memory cache it was registered to refresh never heard about it,
so the change only took effect when the process restarted and re-read the
database at boot.
2. Synchronizations were a single flat per-thread list, fired from index 0 by
whichever transaction completed first. A REQUIRES_NEW inner transaction
therefore fired the *suspended* outer transaction's callbacks too — early,
with the inner transaction's outcome, for a transaction that might still
roll back. Each new transaction now records how many synchronizations were
already registered when it began, and fires only its own tail.
Both managers get the fix and the same callback ordering: unbind the session or
connection first, so a callback that opens its own transaction (a cache reload,
an outbox drain) gets a fresh one instead of joining the transaction that just
committed, then fire, then clean up.
Also fixes JdbcTxStatus rejecting a null connection, which turned the two
propagations that deliberately produce a connectionless status — SUPPORTS with
no active transaction, and NOT_SUPPORTED — into an NPE inside begin(). The
Hibernate manager always allowed it, and resource() already reports the real
mistake with a message that names it.
Tests: 16 new across the two managers, kept deliberately parallel since the two
are interchangeable behind TxManager — synchronization firing, ordering,
per-transaction scoping, callbacks opening their own transaction, and the
previously untested SUPPORTS/NOT_SUPPORTED/MANDATORY propagations. Nothing
covered afterCommit before, which is how both defects shipped.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Its maven-deploy-plugin was configured with <skip>true</skip>, so the
first successful publish-maven.yml run deployed flash-parent, flash,
and every flash-ext-* jar to Gitea's Maven registry, but not
flash-extensions itself — the pom-packaging aggregator every
flash-ext-* submodule's effective POM inherits from via <parent>.
Remote consumers (Pathway's Docker build, resolving flash-ext-* from
the registry instead of a local ~/.m2 install) couldn't resolve that
parent POM and failed with "artifacts could not be resolved:
flash-extensions:pom (absent)".
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Root cause of the persistent 401s found: Gitea's own per-job GITEA_TOKEN
cannot publish to any package registry at all — a known, still-
unimplemented limitation (go-gitea/gitea#23642), not a settings.xml
auth-format issue as first assumed. Confirmed by testing: GITEA_TOKEN
authenticated fine against the plain API but every registry write
endpoint rejected it regardless of scope or header style.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Basic auth didn't fix it either — same 401 as the httpHeaders form.
Before guessing again: confirm GITEA_TOKEN actually reaches this step
non-empty, and check whether it authenticates against the plain API at
all (both header styles), independent of Maven/wagon-http.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Still 401 after granting packages:write — the httpHeaders form from
Gitea's docs is honored by Maven's resolver (dependency reads) but
apparently not reliably by the wagon-http provider maven-deploy-plugin
uploads through, which deployed unauthenticated. Basic auth is
wagon-http's oldest, always-supported path.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Deploy failed with 401 Unauthorized: this repo's default Actions token
permission mode is Restricted (read-only on packages, not Permissive),
so the auto-provided GITEA_TOKEN couldn't push to the Maven registry
without explicitly requesting write access.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
actions/checkout@v4 is a Node-based action; the maven:3.9-eclipse-temurin-21
container it was running in has no Node, so it failed with "node: executable
file not found in \$PATH". A shell git clone needs neither Node nor any
action runtime, just git (installed here via apt).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The "org" runner label is scoped to the Pixel-Services organization —
Flash5 lives under the separate Relism account, which can only reach
the global runner (labels ubuntu-latest/ubuntu-24.04/ubuntu-22.04).
This job just runs Maven inside a container, no Docker access needed,
so ubuntu-latest is a fine fit.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
The org's Gitea Actions runner is registered under the label "org" —
"docker" doesn't match anything online, so the publish job never got
picked up.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lets Pathway (and other consumers) resolve dev.relism:flash from Gitea's
Maven registry instead of requiring a local `mvn install`. Every push
stamps all modules with a commit-scoped version (2.1.0-<short-sha>) before
deploying, since Gitea's registry won't let a build overwrite an existing
name+version.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Data/RepositoryFactory/HibernateData give applications one cached,
stateless entry point for repositories per entity type instead of
per-request instantiation, with write()/afterCommit() replacing manual
transaction+reload choreography.
McpConfig.rolesClaimPath is removed - MCP now derives the claim path
from OidcMiddleware.rolesClaimPath() so applications never duplicate
the roles-claim config between OIDC and MCP. McpPackageScanner is
rebuilt on the shared PackageScanner.discover(packageName) primitive,
doing only McpTool/McpResource/McpPrompt classification itself.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Streamable HTTP transport (JSON-RPC 2.0 over POST), one-class-per-tool/resource/prompt
API mirroring RequestHandler, boot-time-precompiled schema/list payloads for a zero-alloc
hot path, and optional OAuth2 protection built on flash-ext-oidc (lazy-loaded, RFC 8707
audience binding, RFC 9728 Protected Resource Metadata). Registers the module in the
root and flash-extensions POMs and adds the ext-mcp commit scope to AGENTS.md.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Introduces AssetDirectoryScanner, StaticFrontendStrategy, and WebBundlerBuild for
build-time asset discovery, plus config/docs updates for frontend-type resolution.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds the QUERY method (RFC 10008) — safe and idempotent like GET,
but carries a request body like POST, useful for complex filters
that don't fit in a URL query string.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- TlsConfig.applicationProtocols(String...) sets the listener's negotiable
ALPN protocol list via SSLParameters, inherited by every accepted socket
like clientAuth — works on both keystore() and ofContext(), untouched
unless called. Enables TLS-ALPN-01 (RFC 8737) style on-demand cert
issuance: a custom KeyManager can read the already-resolved protocol via
engine/socket getHandshakeApplicationProtocol() inside
chooseEngineServerAlias/chooseServerAlias, since ALPN is resolved during
ClientHello/ServerHello, always before Certificate production.
- Request gains isSecure()/sslSession(), threaded through RequestParser from
the accepted SSLSocket exactly like remoteAddress() — reference-only,
zero per-request allocation. sslSession() defers to SSLSocket#getSession()
lazily, so it's a cached-field read (handshake already completed by the
time a handler can call it), never a forced handshake.
- WebSocketSession.isSecure()/sslSession() delegate to the upgrading
Request rather than tracking the socket a second time.
- Documents TLS end-to-end in README.md (listeners, TlsConfig, SNI, ALPN,
mTLS, Request/WebSocketSession accessors).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Flash can now serve HTTPS and WSS, on one or many listeners per app:
- FlashConfiguration gains an optional `tls` field for the single default
listener, and a `listeners` list for apps that bind multiple ports (each
independently plain or TLS).
- New dev.relism.flash.tls package: TlsConfig.keystore(path, password) builds
an SSLContext from a PKCS12/JKS keystore, with SNI-based certificate
selection for free when the keystore holds more than one alias (matched by
SAN/CN, pure JDK APIs). TlsConfig.ofContext(sslContext) is a full escape
hatch — Flash never calls setSSLParameters on that path, so caller-set
protocols/cipher suites/ALPN survive untouched. TlsConfig.clientAuth(...)
adds optional/required mTLS on either path.
- HttpServer moves from a single ServerSocket to a list of bound listeners;
the per-request hot path (RequestParser, routing, response writing) is
untouched — TLS only changes which bytes come out of accept(), so WSS needs
no separate code path from WS.
- process()'s catch is widened to log non-IOException failures (e.g. a
misbehaving custom KeyManager/TrustManager on the ofContext path) instead
of swallowing them silently; the failure was already isolated to the one
connection via the existing try-with-resources/executor-submission
boundary — this only fixes the missing log line.
- Fixes a pre-existing gap where FlashConfiguration#host was accepted but
never used to bind (listeners always bound to the wildcard address).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- WebSocketSession supports client-mode outgoing frame masking (RFC 6455) via
in-place XOR, reusing the unmask routine already used for inbound frames.
- HeaderMap gains an allocation-free forEach(HeaderConsumer) for callers that
must handle an open-ended set of header names (e.g. proxying).
- HttpServer relays streaming/chunked response bodies through a shared
per-connection ThreadLocal buffer instead of relying on InputStream#transferTo
(which allocates internally) or a fresh byte[8192] per chunked write.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
- Move JteStaticServing to dev.relism.flash package
- Fix imports in HttpStatus and test files
- Add fluent config methods to JteExtension (templateRoot, serveStatics, staticPrefix, etc.)
- Implement routes() for static asset serving with HEAD support
- Include Main.java entry point