The per-room tail was chained with .then(run), so a rejected run skipped every later report for that room and left the cleanup as an unhandled rejection. Chain on both outcomes and clean up on both. A test reaches it with a presence failure plus a throwing logger and fails on the old chain.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
CultLib 286a9af scoped the two packages under @gamecult. The directories under packages/ keep
their short names; only the bare specifiers change.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An ack posted her m.read, so the receipt tracked the queue, not the model. The mouth now posts m.read only
at the event id Huginn reports in gamecult.persona.chat_read.v1, for an event it delivered to her in that
room, forward only, brake read fresh. The ack posts nothing. Presence is online within presenceGraceMs of
a read report, a send or an ack, unavailable after, offline under the brake; a change is PUT at once.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The identity becomes the Matrix localpart at SSO, so a new account must not mint a name that
a Matrix account already holds (the 13 Synapse localparts) or a system name. Native register
refuses a reserved name; a derived identity takes the next free suffix. Lookups of existing
accounts and existing rows are untouched; there is no exemption path.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Fails at this commit with DbUpdateConcurrencyException from CloseMotionAsync:
Motion.Status became a concurrency token in BifrostDbContext.cs:288.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A __Host- cookie is only removed by a Set-Cookie that is itself Secure. The
hardening test now checks Secure on every header, deletion included, and the
completion tests pin that success and refusal both clear the attempt cookie
with Secure, Path=/ and no Domain.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A signed-in retract on a full account table must say busy with no sign-in offer (the
retract door passes its voter to Throttled), and a spent cast budget must say only
"too fast", for anonymous and signed-in voters alike. Both mutants survived Soul s4.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
- answerInvites takes a fresh brake read after the long-poll
- readUpTo reads the brake when each receipt would post; held brake drops it, one log line
- the brake read and offline presence PUT precede the full-backlog early return
- a throwing logger cannot reject the unawaited receipt call
- tests: fake keeps the raw path and honours abort signals; the braked-round carve-out is offline presence PUTs only
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
An ack posts her public m.read per room at the newest covered event, fire and
forget (never awaited, failure logged once, no retry). syncOnce reads the brake
once per round, passes it to answerInvites, sends set_presence on /sync and one
presence PUT on a released-to-braked transition (first braked round included).
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
The room directory's gid must equal the Persona directory's, else store-failed (a setgid
directory in the wrong group would still give files the wrong group). A missing Persona
directory is refused rather than created; only the room directory is made. The stat is
injectable so the group case is testable where the sandbox cannot chown.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A chmod by bifrost, which is not in the Persona's group, clears the setgid bit, so files
would take bifrost's group and the Persona's reader could not open them. Directories and
files are now created with exact modes (fixed umask around mkdir and open), the room
directory must have inherited setgid from the installer's Persona directory or the image
is refused (store-failed), and the Persona directory is never chmodded. Adds behavioural
redirect-refused and fetch-timeout tests.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
m.image events in delivered messages are fetched as the receiving Persona,
classified by magic bytes, bounded by size and header pixel count, and stored
under IMAGE_STORE_ROOT/<persona>/<room hash>/<bytes hash>[.ext] (0640, dirs
2750). The delivery tuple gains index 11, nil or [path, mime, size, sha256];
the pending kind becomes gamecult.persona.chat_pending.v2 with Huginn's v3
golden bytes. Refusals keep the typed text line plus a fixed-word reason.
The mouth test homeserver is answered in-process so the suite runs under a
capped address space.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Session, OAuth correlation, antiforgery, voter and Heimdall attempt cookies all take the
__Host- prefix. The prefix forbids a narrower Path, so the voter (was /mill) and attempt
(was /auth/heimdall) cookies move to Path=/.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
m.image/m.file/m.audio/m.video reached Personas as a bare filename and
dropped content.url. The delivery text now leads with
[kind filename mime size url], then the caption. Applies to the delivery
and to context lines. Wire v1 is unchanged. Nothing is fetched: a Persona
turn is a tool-less text call.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A send queued at the room gate passed the brake check at entry only, so a
brake placed while it waited still let the PUT through. The check now also
runs inside the gate, just before the PUT, and answers a typed Brake refusal.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>