The cdn.provider.<id> health check returned UNKNOWN ("CDN provider does not
support connection tests") for any adapter without CdnOperations, i.e. the
NONE placeholder and Souin. The central evaluator never alerts or changes
state on UNKNOWN, so such a check is permanently useless.
- New optional SPI CdnHealthProbe/CdnHealth; the health check prefers it,
then the connection test, then reports UP with testable=false.
- CloudFront implements it: GetDistribution -> UP when enabled and
Deployed, DEGRADED while InProgress, DOWN when NoSuchDistribution or
disabled; throttling/5xx/network errors DEGRADED; rejected or missing
credentials DOWN. On AccessDenied it falls back to GetDistributionConfig
(the kof22 policy grants that, not GetDistribution) and reports
testable=false; if both are denied, UP with testable=false.
- CloudWatch AWS/CloudFront 5xxErrorRate (Average, 5-min periods, last 30
minutes) goes into details; above 5% the check is DEGRADED. Best effort.
- Messages are fixed text; details hold only booleans, numbers and known
states.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The producer read a literal version, so released artifacts reported a stale
or -SNAPSHOT version. qbit-cdn.properties (version=${project.version}) is
Maven-filtered and the producer loads it; tests assert producer == bundle ==
artifact version.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The bundle version is now Maven-filtered, so a literal SNAPSHOT in the test
fails every release build. Surefire passes the artifact version in
qbit.artifactVersion.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On release branches the bundle's version and the producer's differed, so the
bundle test that asserts they match failed, and released bundles recorded
SNAPSHOT as their provenance. help/*.yaml is now filtered:
version: ${project.version}.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Host-neutral help (deployment-specific facts removed), registered with
HelpBundles, gated by HelpBundleAssertions. QQQ 4.1.0-RC.7.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
onlyWhenFieldsChange was only an allow-list, so a rule could not say
"purge on any change except this derived counter" without listing every
rendered field. An entry starting with "!" now names an ignored field:
with only ignored entries, any other changed field still purges; with a
mixed list the allow-list applies and ignored fields never count.
Inserts and deletes are unaffected. No schema change.
For kof22: the cms_blog_post rule gets "!viewCount" so the nightly view
count refresh purges nothing (Website-Backend#206).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With no live CDN (provider NONE, or every hostname at stage OFF) the
Purge From CDN, Purge Everything and Retry Purge actions said the
requests were "sent within seconds" while the dispatcher skipped them.
Review and Result now carry a warning line ("No CDN provider configured:
purges are recorded only, not sent." or "CDN hostnames are not live"),
from the same status logic CdnPurgeService records (sendingStatus).
The home's Connection, Health and Purges cards say the same, and the
Origin headers card no longer implies a check is scheduled.
Refs KofTwentyTwo/Website-Backend#199
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the hand-rolled HMAC in RevalidationHookRunner with core
WebhookSigner (Standard Webhooks) from QQQ 4.1.0-RC.5, and send through
core SafeHttpClient.
New cdn_revalidation_hook fields:
- signatureFormat: STANDARD_WEBHOOKS (the default for new rows) or LEGACY,
which keeps X-Revalidate-Signature byte-for-byte. A null format, as on
existing rows, stays LEGACY.
- allowPlainHttp and internalAllowlist: explicit exceptions to the SSRF
policy.
Closes#15 (architecture review QRun-IO/qqq#1150, finding F11).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
{blog.slug} used the prefix as both the FK field name and the table name, so
apps with prefixed tables (cms_blog) silently lost those purge paths. The
joined table now comes from the <prefix>Id field's possible-value source,
falling back to the bare prefix. A missing joined table is logged once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Readable at every width without inner scrolling: label/value blocks are dl grids, hostnames and recent purges are lists (passes the kof22 widget readability guard).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- spi.CdnOperations (connection test, stats import, drift) implemented by the CloudFront adapter.
- Test Connection / Import Edge Stats / Check Config Drift as standard ETL actions on cdn_provider, plus daily scheduled stats import and drift/connection checks; findings are opened, kept and resolved.
- Checkstyle now runs in verify with the repo's config and an Apache header file; violations fixed.
- WireMock removed (Jetty conflict with the QQQ BOM); tests use a JDK HttpServer stub.
- README describes the complete qbit.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- TAG batches: PURGE <api> with Surrogate-Key "a, b" (Souin's default
surrogate storage splits on ", ").
- PATHS batches: POST <api> {"type":"uri-prefix","selectors":[...],
"purge":true}, trailing "*" dropped, host-agnostic selectors.
- API URL from setting apiUrl or zoneOrDistributionId; a bare origin gets
the default /souin-api/souin. Synchronous: 2xx is SUCCEEDED.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- CloudFrontCdnProvider: PATHS/ALL batches -> CreateInvalidation with the
batch caller reference as CallerReference (idempotent), IN_PROGRESS with
the invalidation id; checkPurge -> GetInvalidation. TAG/HOST batches are
refused. Throttling, TooManyInvalidationsInProgress, 429/5xx and network
failures are retryable; AccessDenied, InvalidArgument, NoSuchDistribution
and other 4xx are permanent. No SDK-level retries.
- testConnection: GetDistributionConfig + ListInvalidations(MaxItems=1) to
prove IAM scope, optional GetDistribution for domain/status, reports
whether X-Origin-Verify is configured without reading out its value.
- importStats: CloudWatch GetMetricData (AWS/CloudFront, DistributionId +
Region=Global, daily) into the new spi CdnEdgeStat.
- readConfig + CloudFrontDriftCheck.compare against the tofu
expected_config output (docs/cloudfront-expected-config.md).
- Credentials: env vars <secretRef>_ACCESS_KEY_ID / _SECRET_ACCESS_KEY.
- Tests use a JDK HttpServer stub (WireMock cannot start: the QQQ BOM pins
Jetty 12) with hand-built fixtures; CloudWatch (RPC v2 CBOR) is mocked.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- cdn_ tables (provider, hostname, cache policy, table tag rule, purge request, revalidation hook, edge stat daily, origin check, drift finding) with possible values and one permission checker (cdn.view / purge / purgeAll / admin; history tables are never written through the UI).
- Record-change listener -> tag/path rules (published filter, watched fields, xId joins, hub paths) -> cdn_purge_request rows (PENDING, or SKIPPED_* before a cutover).
- Dispatcher: flush window, merge, revalidation hooks first (HMAC-signed, DNS fan-out to every pod), tag or budgeted path planning, async poll, retry with backoff, DEAD.
- Response header policy from cdn_cache_policy (safety rule for auth/cookies; CDN-Cache-Control and tag headers per provider dialect).
- Actions as standard streamed ETL (Purge From CDN per purgeable table, Retry Purge, Purge Everything) and the scheduled dispatcher.
- Admin -> CDN app: sections Connection/Settings/Activity/Performance, home widget with honest empty states, Health status card.
- Tests on the memory backend: producer + instance validation, pipeline end to end, hooks, policy, ETL review/result, home view, permissions.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>