Repository source: docs/plans/image_content/image_content_step4_implementation_summary.md
Debug mode: disabled
Stage 1 - Portable storage and feature flag
Changes: Added strict versioned body references, immutable private filesystem store, upload/store limits, and default-off registered image flag. Documented the canonical text contract; no database migration.
Verification:
php tests/run.php ImageContentTest FeatureFlagEvaluatorTest— 18 passed; reference round-trip, corruption, traversal/symlink roots, dimensions, idempotent storage and flag precedence checked. Updated the registry enumeration expectation for the new flag.Notes: Original bytes are retained; future bucket storage can preserve digest identity. Store mutations will use the existing instance write lock. UI and serving arrive in later stages.
Stage 2 - Signed attachment publication
Changes: Prepared posts bind portable image metadata in their signed body. Finalization accepts a server-verified multipart upload, verifies signature/approval/flag/checksum/limits under the instance write lock, and saves retained bytes only after the canonical commit. Unsigned thread/reply/quote writes reject image references. Image finalization retains its expiring preparation token for safe retries.
Verification:
php tests/run.php WriteApiSmokeTest::testImageSignedPublicationRejectsTamperingAndRetriesSafelypassed with a real GPG signature, tampered bytes, disabled flag, unsigned bypass and duplicate retry checks; changed PHP files pass lint.Notes: A post committed before a failed image save is explicitly reported; retrying the same signed submission repairs the image without a duplicate post. Failed pre-commit uploads remain PHP temporary files, outside retained backups. Preparation-token expiry still applies.
Stage 3 - Shared image compose flow
Changes: Shared optional JPEG/PNG and description controls, lazy signing activation, browser metadata hashing and multipart signed finalization. Image requests retain preparation/signature for same-form retry; anonymous and Outbox actions reject selected images. File inputs are excluded from saved drafts and disabled until signing binds; descriptions/text persist and reload guidance explains reselection.
Verification:
php tests/run.php LazyComposeSigningTest BrowserSigningNormalizationTest ImageContentTest— 82 passed;node tests/browser/image_compose_transport.cjschecks actual helper metadata, multipart bytes, retry reuse, changed drafts and offline rejection; changed JavaScript passes syntax checks.Notes: Image authoring requires HTTPS/localhost Web Crypto; normal text authoring is unchanged. Full browser acceptance is consolidated in Stage 8 after delivery and backups exist.
Stage 4 - Protected rendering and delivery
Changes: Shared body renderer displays local images with escaped descriptions, dimensions and unavailable fallback.
/images/is behind the instance access gate and feature flag, serves only verified digest filenames, and returns no-store/nosniff responses. Generated layouts carry image flag state; FrontController bypasses mismatched static artifacts, including environment override changes.Verification: Renderer/media regressions (14 passed); focused storage/routing/signed publication/static tests (7 passed);
LocalAppSmokeTest::testImageFlagMismatchBypassesStaleStaticArtifactpassed. Public image delivery and flag-off rejection exercised via Application. Existing service-worker allowlist excludes/images/.Notes: Removed Apache QDB direct-content shortcuts so PHP can evaluate live flags/membership; matching artifacts still use PHP's static fast path. This adds PHP dispatch overhead on formerly direct QDB paths. Cached copies already downloaded cannot be recalled.
Stage 5 - Complete revision-bound backups
Changes: Added bounded, serialized image exports with manifest/checksums and repository revision; matched repository snapshots retain Git history. Backup links pin their displayed revision; changed/dirty repositories and missing/corrupt images fail before download. Snapshot capture uses the instance write lock, archiving happens afterward, and temporary space is checked. Retained-image backup is independent of display flag.
Verification:
php tests/run.php ImageContentTest LocalAppSmokeTest::testFrontControllerServesStaticArtifactForBackupAlias— 4 passed, including real tar exports, manifest/object checks, matching revisions and missing/wrong-revision rejection. PHP lint and whitespace checks passed.Notes: Web repository snapshots are bounded to 512 MiB/100000 files; image limits remain 256 MiB/1024 objects. Archive assembly is serialized and requires twice snapshot bytes plus 16 MiB free temporary space. External writers must respect the documented maintenance boundary; detected HEAD/record changes abort capture.
Stage 6 - Verified independent restoration
Changes: Added
./v3 images verify|restore, strict bounded tar validation, full inventory/reference checks, revision matching, and private staging with atomic activation. Existing different stores are preserved; identical-store retries succeed. Added backup/recovery runbook and CLI reference.Verification:
php tests/run.php ImageContentTest— 3 passed including verify-only, activation, identical retry, unsafe entries, wrong repository revision and preservation of existing bytes. CLI help, PHP lint, launcher syntax and whitespace checks passed.Notes: Repository snapshot extraction precedes image restore; restore deliberately requires the original revision rather than a content-import commit. Rebuild and new-instance browser verification follow before cutover. Full command/browser recovery acceptance is scheduled for Stage 8.
Stage 7 - Secondary surfaces and deployment guidance
Changes: Offline rendering shows escaped image descriptions with an explicit unavailable message; offline/SQLite tools explain separate image backups. Text APIs retain canonical references and annotate missing binary content; RSS adds a view-online note. Repository-import reports disclose that image bytes are not transferred. Updated deployment guidance, example environment and README links, including QDB routing and future Option C portability.
Verification:
php tests/run.php OfflineSnapshotPresentationTest ImportedContentPublicationTest ImageContentTest— 12 passed; extended offline renderer coverage also passes (7 methods), including escaped descriptions. JavaScript syntax and whitespace checks passed.Notes: Activity/text/SQLite retain readable text references rather than fetching binary content. No offline binary cache or remote storage adapter is introduced. Real host PHP/Apache and browser checks are the next stage; no production host is configured in this task.
Stage 8 - Integrated verification and recovery
Changes: Added a real Chromium/OpenPGP acceptance harness with isolated PHP fixtures and a separate Apache/mod_php routing harness. Browser testing found multipart newline normalization; image finalization now base64-transports the exact canonical signed bytes. Added explicit oversized-request errors, unsigned file rejection, standalone artifact flag markers, and storage stat-cache invalidation exposed by capacity testing.
Verification:
node tests/browser/image_compose_transport.cjspassed.php tests/run.php ImageContentTest WriteApiSmokeTest::testImageSignedPublicationRejectsTamperingAndRetriesSafelypassed all 5 methods, including 1024-object capacity, oversized input and malicious traversal/symlink archives.node tests/browser/image_content_browser.mjspassed twice after fixture stabilization: actual signed thread/reply, lost-response retry without duplicate publication, anonymous draft preservation, image decoding, standard/Forte/QDB rendering, paired downloads, CLI verify/restore/rebuild into a fresh instance, byte equality, flag-off backup and authenticated versus anonymous access. Saved-identity authentication deliberately reloads public pages; the harness now establishes that session before entering the compose draft.Verification:
python3 tests/image_content_apache_test.py <browser-fixture-root>passed against isolated Apache/mod_php using the actual.htaccess: verified bytes/headers, live flag/membership gates, stale static rejection, retained backup with flag off, blocked direct static paths, and a 9 MiB multipart request returning 413 without changing HEAD. No system Apache configuration was changed. Browser harness requiresplaywright-coreand Chrome (overrideCHROMIUM_PATH); Apache harness requires local Apache modules and PHP 8.1 configuration paths.Verification: Final
./v3 testran 1127 tests: 1119 passed, 8 failed. All eight failures reproduce on pre-feature commit530acbe0: six in an isolated baseline checkout, plus the feature-flag locked-badge and queued-agent tests when using the same existing private configuration. Baseline failures areLocalAppSmokeTest::testApplicationRendersCoreRoutes,LocalAppSmokeTest::testFeatureFlagsPageShowsLockedBadgeWithReasonForNonMutableFlags, bothOfflineSnapshotBootstrapTestmethods, bothOfflineSnapshotPublishCommandTestmethods,QuoteCardDisplayNumberTest::testQdbWelcomeDisplaysThreeNewestNewsItemsAndLinksToAllNews, andWriteApiSmokeTest::testTaskQueueProcessesQueuedAgentReplyOnce. These unrelated failures remain unresolved. Session evidence:/tmp/forum-image-final-tests.log,/tmp/forum-image-baseline-full.log,/tmp/forum-image-baseline-config.log,/tmp/forum-image-browser-final.log,/tmp/forum-image-browser-repeat.log, and/tmp/forum-image-apache-final.log(temporary local logs, not durable release artifacts).Notes: The feature remains default-off. No production deployment, external bucket provisioning, or database migration is required or performed. Actual target-host permissions/capacity and PHP limits must be checked before enabling there, following the runbook. Browser automation exercises dedicated thread/reply forms; inline/dialog surfaces use the same controls and signing handler but were not separately driven in the browser. Concurrent external Git writers and process-kill recovery were not fault-injected; snapshot locks/revision checks and restore staging/retry are covered at the component level. Future Option C remains possible through portable digest references; no cloud adapter is shipped.
Handoff: All eight planned stages are complete. Approved plans and navigation were committed first, followed by one commit per implementation stage with its summary update. Documentation, ordinary UI publication/download, CLI recovery, effective feature-flag gating, and local PHP/Apache behavior have been checked. The full regression suite retains the eight reproduced baseline failures above; no production release is claimed.
Follow-up - Optional image descriptions
Changes: Per user request, descriptions are optional in compose and server validation. Empty descriptions omit the final reference field and its separator; existing described references remain valid. Offline/unavailable rendering handles the omitted field. Updated the canonical specification and backup runbook.
Verification: Image storage/reference/backup/restore and offline presentation tests: 11 passed. Compose transport test passed with an empty description. Full browser acceptance passed with a described thread and an undescribed reply, including publication, display, paired backups and fresh-instance restoration.
git diff --checkpassed.