Persona saves a named attachment into her sandbox (BIFROST_CHAT_FETCH, off by default) #64

Merged
eureka merged 4 commits from eureka/persona-save-attachment into main 2026-11-12 02:07:13 +00:00

2026-11-12

Pin the saver's temp-create flags, the compare NOFOLLOW, the fetch UnknownKind wire path and the revised fetch id; document the receipt's meaning
Tests only plus a cfg(test) gap seam between unlinkat and openat in attachment.rs and the
module doc sentence on the file_received receipt (follow-up receipt-outlives-failed-save,
accepted).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-12 02:05:53 +00:00
Persona save_attachment: reply field behind BIFROST_CHAT_FETCH, the fetch step in act, the closed outcome lines
Config.fetch (exactly "1", read in from_args_and_env through the lookup), SaveAttachment
{room, event} on PersonaReply with the tempo_vote_beat_s present() pattern, a conditional
unrequired schema property, SAVE_FAULT refusal when the flag is off or the room/event is
out of bounds, Step::Answered carries zero to two notice lines (open, then save), and a
gated fetch step: NoStore decided before any request, the saver called with the receipt's
room id, one fixed line per outcome with the relative path only on success.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-12 02:05:53 +00:00
Attachment saver: verified chat image into the sandbox by directory fd; load_bytes cut out of load
attachment.rs (crate-private): save(store, sandbox, room_id, image) re-verifies through
chat_image::load_bytes (load keeps its signature and calls it), names the file
<sha256>.<ext> from the verified type, refuses a .git at the sandbox root or in
attachments/, and writes through rustix openat/linkat on held descriptors: temp name
.part unlinked first, 0600 in the create call, fsync, linkat (never replaces), bounded
nonblocking regular-file compare on EEXIST. cfg(windows) half returns WriteFailed.
rustix gains the fs feature; no crate added.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-12 02:05:53 +00:00
Fetch wire: chat_fetch request/receipt documents, Chat::fetch, RudpChat::fetch, Bifrost persona-chat-v7 bytes as persona-chat-v9
First half of cut-persona-save-attachment.r7: the 4-slot request, the 5-slot receipt
(nested ChatImage) with its one validity table (Stored with nil receipt allowed), the
14 outcome words, ReceiptMode, and the exchange() fetch beside open_direct including
UnknownKind -> MouthRefused. Fixtures copied byte for byte from Bifrost 3a6b45d.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-12 02:05:53 +00:00