Commit Graph
24 Commits
Author SHA1 Message Date
james.maesandClaude Opus 5.5 9ff55f4c23 fix(health): real CloudFront health probe; UP/not-testable instead of UNKNOWN
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>
2026-10-16 11:38:34 -05:00
james.maesandClaude Opus 5.5 79ae6264f8 fix: the qbit metadata version comes from the build, not a hardcoded constant
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>
2026-10-15 01:22:59 -05:00
james.maesandClaude Opus 5.5 3d72a5d05d test(help): compare the help bundle version with the artifact version, not a hardcoded SNAPSHOT
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>
2026-10-14 23:07:25 -05:00
james.maesandClaude Opus 5.5 aba80ff62f fix(help): the help bundle carries the artifact version (Maven-filtered) instead of a hardcoded SNAPSHOT
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>
2026-10-14 22:01:28 -05:00
james.maesandClaude Opus 5.5 1262f567c8 Merge origin/develop into feature/health-checks; pin QQQ 4.1.0-RC.8
RC.8 ships the central health kernel (QRun-IO/qqq#1207).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 21:15:03 -05:00
james.maesandClaude Opus 5.5 f7c9e0fce3 feat(help): ship the qbit-cdn help bundle through QQQ help bundles, with coverage gate
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>
2026-10-14 20:17:09 -05:00
james.maesandClaude Opus 5.5 5a0d3e824a fix: hard socket deadline, IPv6 allowlist, port stripping for the origin check
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:12:41 -05:00
james.maesandClaude Opus 5.5 422b34988d fix: pin origin check to the checked address, full address blocklist, header-only read; setup and defaults review fixes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 17:06:20 -05:00
james.maesandClaude Opus 5.5 3980e05719 feat: origin header check, setup card, guided empty states, create-default actions
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 16:57:57 -05:00
james.maesandClaude Opus 5.5 d3995734dd refactor(cdn): drop the HTML health card and the scheduled connection test (central health covers them)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 14:55:07 -05:00
james.maesandClaude Opus 5.5 8bb2c4eb4f feat(health): register CDN provider checks with core central health
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 14:55:06 -05:00
james.maesandClaude Opus 5.5 49c0db82f3 feat(purge): "!field" entries in onlyWhenFieldsChange ignore a field
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>
2026-10-14 11:30:17 -05:00
james.maesandClaude Opus 5.5 ecceaf31ee fix(purge): say when purges are recorded only, not sent
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>
2026-10-14 10:39:55 -05:00
james.maesandClaude Opus 5.5 7510392166 feat(revalidation): sign hooks with core WebhookSigner and the safe HTTP client
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>
2026-10-13 19:33:36 -05:00
james.maesandClaude Opus 5.5 26e62f3bcc fix(purge): resolve tag-rule joined tables via the FK field's possible-value source
{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>
2026-10-12 19:41:32 -05:00
james.maesandClaude Opus 5.5 8ef4a0f90e fix(app): CDN home uses definition lists and lists, no tables
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>
2026-10-12 14:48:27 -05:00
james.maesandClaude Opus 5.5 301a244353 feat: configurable table link prefix
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:39:17 -05:00
james.maesandClaude Opus 5.5 8dd553116f feat: configurable app path for links (nested apps)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:36:22 -05:00
james.maesandClaude Opus 5.5 3f74bff401 feat: table metadata customizer in CdnQBitConfig
Lets apps map the cdn_ tables to their naming (e.g. snake_case columns), like the other qbits.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 14:33:10 -05:00
james.maesandClaude Opus 5.5 e6685a7c80 feat: provider actions (test connection, edge stats, drift), checkstyle in verify
- 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>
2026-10-12 14:31:17 -05:00
james.maesandClaude Opus 5.5 85aae17d95 feat(souin): Souin adapter (Surrogate-Key tag purge, uri-prefix path purge)
- 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>
2026-10-12 14:22:02 -05:00
james.maesandClaude Opus 5.5 ae2a9237d4 feat(cloudfront): CloudFront adapter (invalidations, test connection, edge stats, drift)
- 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>
2026-10-12 14:22:00 -05:00
james.maesandClaude Opus 5.5 57cdc24b36 feat: QQQ integration (cdn_ tables, purge pipeline, ETL actions, Admin -> CDN app)
- 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>
2026-10-12 14:18:07 -05:00
james.maesandClaude Opus 5.5 ccf0473c67 feat: provider-neutral core (SPI, cache tags, path invalidation planner, NONE provider)
First P1 slice of the CDN qbit (QRun-IO/qqq docs/specs/2026-10-12-qbit-cdn-design.md).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 11:13:11 -05:00