Skip to content

refactor(cli/purge): firm up --json and --ndjson emit semantics#287

Merged
indaco merged 2 commits intomainfrom
chore/pin-purge-json-emit-atomicity
May 7, 2026
Merged

refactor(cli/purge): firm up --json and --ndjson emit semantics#287
indaco merged 2 commits intomainfrom
chore/pin-purge-json-emit-atomicity

Conversation

@indaco
Copy link
Copy Markdown
Owner

@indaco indaco commented May 7, 2026

Description

Two related cleanups to the purge JSON layer that tighten the emit guarantees scripts depend on. Both ride together because they touch the same file and reinforce the same wire-format contract.

1. --json summary builds into a fixed stack buffer. Previously the summary used a std.Io.Writer.Allocating for atomicity - all-or-nothing today, but as a side-effect of how the allocating writer batches. A future drift to incremental flushing could let a --json consumer observe a half-document. The summary now mirrors the ndjson shape: build into a 2 KB stack buffer sized for the closed schema (7 scopes, longest scope name 13 chars, longest error_kind token 17 chars, max u32 per-row removed counter, max u64 byte counters - worst case ~1.1 KB), write the buffered bytes in a single call, drop oversized payloads silently. Atomicity is now structural; there is no allocator dependency and no possibility of a torn document. emitSummary no longer takes an allocator parameter, simplifying both call sites in purge.zig.

2. --output-format=ndjson per-event buffer aligned to 1 KB. The purge ndjson emitter used a 512-byte line buffer while the install-side output.emitNdjsonEvent peer used 1024. A future schema field that pushes a purge line over 512 bytes would have hit the silent-drop branch on the closed scope set, even though output's own reasoning ("worst-case adversarial-escape name still fits in 1 KiB") leaves headroom. Both emitters now share the same ceiling, and the silent-drop fallback is pinned by a regression test feeding an oversized scope name - "drop, not crash" is a tested invariant rather than a comment.

Tests pin the at-budget worst case for --json, the overflow drop for --json, and the overflow drop for --ndjson so future schema growth that breaks any of these budgets trips a unit test instead of truncating output.

Related Issue

  • None

Notes for Reviewers

  • None

Drop the allocator-backed buffer and mirror the ndjson shape: the
summary now builds into a 2KB stack buffer sized for the closed
schema and writes the buffered bytes in a single call, dropping any
oversized payload silently. The all-or-nothing emit guarantee is
structural now rather than an emergent property of how the allocating
writer batches. Tests pin both the at-budget worst case and the
overflow drop so a future schema bump trips a unit test instead of
truncating output.
@indaco indaco force-pushed the chore/pin-purge-json-emit-atomicity branch from ecaafae to c1b3a1d Compare May 7, 2026 13:13
@indaco indaco changed the title chore(cli/purge): pin --json all-or-nothing emit contract refactor(cli/purge): build --json summary into a fixed stack buffer May 7, 2026
Bump the per-event line buffer in the purge ndjson emitter from 512
to 1024 bytes so it matches output.emitNdjsonEvent and absorbs the
same worst-case adversarial-escape names. The silent-drop fallback
is now pinned by a regression test feeding an oversized scope name -
"drop, not crash" is a tested invariant rather than a comment.
@indaco indaco changed the title refactor(cli/purge): build --json summary into a fixed stack buffer refactor(cli/purge): firm up --json and --ndjson emit semantics May 7, 2026
@indaco indaco merged commit b2d065c into main May 7, 2026
3 checks passed
@indaco indaco deleted the chore/pin-purge-json-emit-atomicity branch May 7, 2026 13:19
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