Skip to content

Release 1.44.0 — Errai: Closing the Trust Gaps

Choose a tag to compare

@MichaelSowah MichaelSowah released this 22 May 01:02
· 194 commits to main since this release

Summary

A focused follow-up to Dabih that reconciles four places where the README, CLI, or public API advertised behavior the code didn't deliver. The 1.43.0 release raised the framework's
credibility surface, which made the remaining gaps more damaging, not less. This PR closes them.

The four trust gaps (TG-1 through TG-4):

  • TG-1 — cache tagging across all drivers
  • TG-2security:report fabricated metrics
  • TG-3 — whitelist analyzer iterating placeholder route data
  • TG-4ArchiveService::restoreFromArchive() always failing

Scope note: main does not yet contain the Dabih (1.43.0) commit, so this PR also rolls in bab993b and the associated a01ed55 CI fix. If Dabih has already been merged via a
separate PR, please rebase before merging.

What's changed

TG-1 — Real tag-aware cache invalidation on Redis

RedisCacheDriver::addTags() and invalidateTags() are now backed by Redis SETs (_gf_tag:{tag} → set of cache keys):

  • Pipelined SADD on association (idempotent — repeated calls don't duplicate).
  • Bulk DEL (keys + tag sets) on invalidation.
  • getCapabilities()['features']['tags'] flipped to true.

This unblocks the four real callers that previously failed silently: QueryCacheService, DistributedCacheService, ResponseCachingTrait, and php glueful cache:clear --tags.

Memcached and File drivers remain 'tags' => false — with explicit documentation (Memcached lacks set primitives; File would need a separate index layer) instead of the previous misleading
"Not implemented yet" TODO. README cache claim narrowed to "tag-based invalidation on the Redis driver."

TG-2 — Honest security:report

Stripped every fabricated section:

  • Removed analyzeAuthenticationSecurity(), getAuditSummary(), runVulnerabilityAssessment(), gatherSecurityMetrics() — all returned rand() values across 12+ fields.
  • Removed sendReportByEmail() — only printed a "would be sent" message.
  • Removed assessCompliance() — returned hardcoded 'Partial' / 'Enabled' strings unconnected to any real signal.
  • Removed --include-vulnerabilities, --include-metrics, --email, --days options.
  • Removed the PDF format (never implemented).

The command now exports HTML/JSON/text reports of the production readiness score, environment configuration, system info, and derived recommendations only. For dependency CVE scanning, users
are directed to security:vulnerabilities (which uses a real VulnerabilityScanner).

TG-3 — fields:whitelist-check inspects real routes

analyzeWhitelistCompliance() now reads Router::getStaticRoutes() and Router::getDynamicRoutes() and inspects each Route::getFieldsConfig() for the actual #[Fields] attribute data
(allowed list, strict flag). The previous placeholder loop iterated a hardcoded three-entry list (api.users.index, api.posts.show, api.admin.users) regardless of the application's
actual routes.

While in the file, added a new low-severity NON_STRICT_WHITELIST finding for /api/ routes with a non-strict whitelist (disallowed fields are silently dropped instead of rejected), and
removed the fabricated pattern_frequency block (65/25/10). The renamed getReferenceFieldPatterns() helper now documents itself as static defaults seeding --suggest-whitelist, not
telemetry.

TG-4 — Real archive restore

ArchiveService::restoreFromArchive() previously returned RestoreResult::failure("Restore functionality not yet implemented") regardless of input. It now replays archived rows into a
target table inside a Connection::transaction():

  • Honors ArchiveRestoreOptions: targetTable (defaults to source), offset/limit, and conflictResolution (skip records collisions in the result; overwrite hard-deletes the
    existing row via raw PDO to bypass soft-delete, then re-inserts).
  • Primary key detection prefers uuid then id.
  • Rejects unsupported options (rename, auto-create) with explicit, typed failures.

The existing loadArchive() already handled checksum verify + decrypt + decompress; only the row replay was missing. Also fixed a latent SQLite bug in validateTable() where PRAGMA's empty
result for missing tables was treated as success.

Files changed

Source (8 files):

  • src/Cache/Drivers/RedisCacheDriver.php, MemcachedCacheDriver.php, FileCacheDriver.php
  • src/Console/Commands/Security/ReportCommand.php
  • src/Console/Commands/Fields/WhitelistCheckCommand.php
  • src/Services/Archive/ArchiveService.php
  • src/Database/ORM/Model.php (loop variable shadowing fix)
  • src/Support/Version.php (→ 1.44.0 / Errai / 2026-05-22)

Tests (5 new files, all passing):

  • tests/Integration/Cache/RedisCacheDriverTagsTest.php — 9 tests against real Redis, skips cleanly when unreachable
  • tests/Unit/Cache/UnsupportedTagsContractTest.php — pins Memcached/File no-op behavior
  • tests/Unit/Console/Commands/Security/ReportCommandTest.php — pins post-strip contract via CommandTester
  • tests/Unit/Console/Commands/Fields/WhitelistCheckCommandTest.php — exercises analyzer against a real Router via reflection
  • tests/Integration/Services/Archive/ArchiveRestoreTest.php — full round-trip against temp SQLite

Docs:

  • CHANGELOG.md, ROADMAP.md, README.md, docs/FRAMEWORK_IMPROVEMENTS.md

Breaking changes

  • security:report output shape changed. Consumers parsing JSON should expect authentication, audit_summary, vulnerabilities, metrics, and compliance keys to be absent. The
    removed --include-vulnerabilities, --include-metrics, --email, --days options now throw InvalidOptionException. PDF format gone — only html, json, text accepted.
  • fields:whitelist-check reports real routes now. Output is meaningful but different from before. Routes without #[Fields] or with non-strict whitelists may surface as findings.

Non-breaking behavior changes worth flagging

  • Cache tagging is Redis-only. Calls on Memcached and File drivers continue returning false (unchanged), but the capability is now documented as deliberate. Branch on
    getCapabilities()['features']['tags'] for driver-agnostic behavior, or switch to Redis for real invalidation.
  • restoreFromArchive() no longer always fails. Code that called this and treated the failure as expected (e.g., catch-and-log scaffolding) should be reviewed.

Coordinated repos

  • api-skeleton v1.27.0 (3658b84) — bumps glueful/framework to ^1.44.0. No new migrations.
  • docs site (b95d44e) — adds v1.44.0 release page entry.

Test plan

  • PHPStan clean (no errors at the configured level)
  • composer test — 887/887 passing (5 new test files contribute 38 assertions across 28 tests)
  • composer run phpcs clean
  • CI green on this PR
  • Manual smoke: php glueful security:report --format=json shows only real sections
  • Manual smoke: php glueful fields:whitelist-check reports real routes
  • Manual smoke (with Redis): cache tag invalidation actually invalidates

Upgrade

composer update glueful/framework

No new migrations. No env var changes.