Python's text layer applies universal-newline translation: CRLF becomes LF
on read, and on Windows LF expands back to CRLF on write. Every CLI path
that carried artifact and event content went through it, so a patch piped
in from a Windows agent was stored as different bytes than the ones git
produced — under a sha256 of the mangled content, which then failed to
match on the machine that applied it. For a store whose contract is
verbatim bytes addressed by digest, that is silent corruption.
It also disarmed the CRLF warning added for kind=patch: put_artifact looks
for a carriage return that the read path had already stripped, so the one
check meant to catch unappliable diffs could never fire on the platform
that produces them.
- _read_stdin_exact() / _write_stdout_exact(): go through sys.stdin.buffer
and sys.stdout.buffer, falling back to the text layer when a replaced
stream has no .buffer.
- --file / --body-file: read_bytes().decode() instead of read_text().
- artifact get --out: write_bytes() instead of write_text().
Verified through real pipes: a 63-byte CRLF patch round-trips at 63 bytes
with an unchanged sha256 via stdin, --out, and stdout, and the CRLF warning
now fires. Regression coverage in TestVerbatimNewlines, confirmed failing
against the old behavior first.
Config reads (peers.json, replica.json, server registry, token file) are
left on read_text: JSON is newline-insensitive and none of them carry user
content.
Reported by Copilot on the pull request.
Reported by windows-codex through the logstream itself: wait rejected
'limit' with -32602 while list accepts it, though the filter sets are
otherwise identical. The core already threaded limit through; expose it
on the MCP schema and the CLI (wait --limit).
(cherry picked from commit bc18ccaac6ec709dbb0821f65c3a8e39f68733d1)
mempalace logstream append/list/wait/ack and mempalace artifact put/get.
Operates directly on the palace logstream.sqlite3 (WAL; safe alongside a
running hub — no Chroma state involved). --json everywhere for scripting;
wait exits 2 on timeout; artifact get prints exact content on stdout so
'mempalace artifact get ID | git apply' works; put reads stdin by default.
(cherry picked from commit 54ca9600e57770fa1e9b46565816fa6ceed8221d)