Conversation
chrsmith
added this pull request to stack #12123
September 16, 2026 22:58
The aggregate size cap needs to know how many bytes of callbacks an execution already holds, and that cannot be derived from the CHASM tree. Child blobs are only serialized when the transaction closes, so a mid-transaction walk sees stale bytes, and there is no exported API for a node's serialized size in any case. Denormalize the totals instead. Add total_callbacks_size to ActivityState and OperationState, and give the CHASM Workflow component a real state message. It previously embedded google.protobuf.Empty because mutable state owns workflow state; it now carries WorkflowState with the count and size, left nil until first written so a callback-free workflow still persists a zero-byte blob. The swap is invisible to the tree, which identifies nodes by registered component type ID rather than proto type, and both encodings agree on the wire. The rolling-upgrade direction that could actually bite is the downgrade: an old server decoding WorkflowState into Empty and writing it back must not drop the new fields, or the counters silently reset to zero and look like data predating them. Tests pin all three directions through the codec the tree really uses. Workflows also get a denormalized count, not just a size. totalCallbackCount walked every WorkflowUpdate on each attach, and under a MutableContext that Get also marks each node dirty, so attaching one callback re-serialized every update. That now happens only during the one-time backfill. Backfill needs no migration or marker: a stored size of zero alongside a non-empty callback set can only be pre-field data, because a validated callback always serializes to more than zero bytes. The recomputed total is written back even when nothing was inserted, so it happens at most once per execution. Limits are now charged against what a call would actually persist rather than the request as sent. The insert loops already skipped keys a retry re-derives, so charging the full request could reject a retry for exceeding a cap it does not move. Planning the insertions first also stops a rejected update request from leaving a half-built update component behind. No limit changes: the size cap is still disabled by default. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
chrsmith
force-pushed
the
chrsmith/cb-2-standalone-components
branch
from
September 17, 2026 15:50
66a9bda to
efe7c20
Compare
chrsmith
force-pushed
the
chrsmith/cb-3-denormalize-totals
branch
from
September 17, 2026 15:50
8a0f294 to
e6bdcf4
Compare
chrsmith
removed this pull request from stack #12123
September 18, 2026 19:21
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Stack 3/6 — base:
chrsmith/cb-2-standalone-components(#12118)Why
The aggregate size cap needs to know how many bytes of callbacks an execution already holds, and that cannot be derived from the CHASM tree. Child blobs are only serialized when the transaction closes, so a mid-transaction walk sees stale bytes, and there is no exported API for a node's serialized size in any case.
What
total_callbacks_sizeonActivityState(24) andOperationState(21).Workflowcomponent gets a real state message. It previously embeddedgoogle.protobuf.Emptybecause mutable state owns workflow state; it now carriesWorkflowStatewith the count and size, left nil until first written so a callback-free workflow still persists a zero-byte blob.Workflows also get a denormalized count, not just a size.
totalCallbackCountwalked everyWorkflowUpdateon each attach, and under aMutableContextthatGetalso marks each node dirty — so attaching one callback re-serialized every update. That now happens only during the one-time backfill.Compatibility — the part worth scrutinising
The swap is invisible to the tree, which identifies nodes by registered component type ID rather than proto type, and both encodings agree on the wire.
The direction that could actually bite is the downgrade: an old server decoding
WorkflowStateintoEmptyand writing it back must not drop the new fields, or the counters silently reset to zero and look like data predating them.workflow_state_compat_test.gopins all three directions through the codec the tree really uses.Note
chasm/lib/buf.yamlbreaking checks are not wired (Makefile:508TODO), so CI will not catch proto issues here.Backfill
No migration and no marker field. A stored size of zero alongside a non-empty callback set can only be pre-field data, because a validated callback always serializes to more than zero bytes. The recomputed total is written back even when nothing was inserted, so it happens at most once per execution.
Also
Limits are now charged against what a call would actually persist rather than the request as sent. The insert loops already skipped keys a retry re-derives, so charging the full request could reject a retry for exceeding a cap it does not move. Planning the insertions first also stops a rejected update request from leaving a half-built update component behind.
No limit changes: the size cap is still disabled by default.
🤖 Generated with Claude Code