Skip to content

ActivityLogger write-cap + activity-log list projection (KYTE-#182)#99

Merged
kennethphough merged 1 commit into
masterfrom
fix/activitylog-write-cap-182
Jun 1, 2026
Merged

ActivityLogger write-cap + activity-log list projection (KYTE-#182)#99
kennethphough merged 1 commit into
masterfrom
fix/activitylog-write-cap-182

Conversation

@kennethphough

Copy link
Copy Markdown
Member

KYTE-#182 — KyteActivityLog unbounded growth → admin log OOM

KyteActivityLog grew to 10GB on one install and OOM'd the admin log query. Two independent causes, both fixed here — pure code, no schema change, instant rollback (revert the composer version).

1. Write-cap (durable bloat fix)

request_data and changes are LONGTEXT; ActivityLogger::log() stored the full json-encoded request body / diff. A page or script save carries 300KB+ of HTML/JS/CSS, so each logged row could be hundreds of KB — that is what filled the table.

log() now caps both fields at KYTE_ACTIVITY_LOG_MAX_FIELD_BYTES (default 16384, auto-defaulted in Api::$defaultEnvironmentConstants). Over-limit values are replaced with a small audit-preserving marker:

{"_truncated":true,"_original_bytes":312488,"_fields":["html","javascript","stylesheet"]}

so you still see what was sent/changed and how big it was, without the megabyte of content. Set the constant to 0 to disable. Redaction (SensitivityPolicy + hardcoded SENSITIVE_FIELDS) is unchanged and still runs before the cap.

2. List-view projection (OOM fix)

The framework SELECT * (DBI::select) pulls both LONGTEXT columns for every row, but the admin log list never reads them — only the single-record detail view does. KyteActivityLogController now detects a by-id detail fetch in hook_prequery and, on every other (list) response, omits request_data/changes and skips the decode. The Shipyard detail view (get("KyteActivityLog","id",idx)) still returns the full decoded payload — no Shipyard change required (verified: the list JS never references these fields; the detail JS fetches a single record by id).

Not in this PR

Index coverage and a retention policy are deferred to the data-model initiative (KYTE-#190). The earlier one-off ETOM purge (10.4GB → 1.56GB) was the band-aid; the write-cap here prevents recurrence.

Tests

Adds tests/ActivityLoggerWriteCapTest.php (oversized → marker, within-limit → verbatim, oversized PUT diff → marker). Existing ActivityLoggerSensitivityTest covers the unchanged redaction/drop paths.

🤖 Generated with Claude Code

KyteActivityLog grew to 10GB on one install and OOM'd the admin log
query. Two independent causes, both fixed here as pure code (no schema
change, instant rollback).

1. Write-cap: request_data and changes are LONGTEXT and ActivityLogger
   stored the full json-encoded request body / diff. A page or script
   save carries 300KB+ of HTML/JS/CSS, so each row could be hundreds of
   KB. log() now caps both fields at KYTE_ACTIVITY_LOG_MAX_FIELD_BYTES
   (default 16384, auto-defaulted in Api). Over-limit values become a
   small audit-preserving marker {_truncated, _original_bytes, _fields}.
   Set the constant to 0 to disable. Redaction still runs before the cap.

2. List projection: the framework SELECT * pulls both LONGTEXT columns
   for every row, but the admin log list never uses them — only the
   single-record detail view does. KyteActivityLogController detects a
   by-id detail fetch in hook_prequery and omits request_data/changes
   (and skips the decode) on every list response. The Shipyard detail
   view (get by id) still gets the full decoded payload, so no Shipyard
   change is required.

Indexes + retention deferred to KYTE-#190. Adds ActivityLoggerWriteCapTest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@kennethphough
kennethphough merged commit 796f3e8 into master Jun 1, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant