Commit graph Bifrost/tests
Author SHA1 Message Date
800486a9a0 Merge pull request 'Pin the vote page busy-refusal branches' (#14) from heimdall/pin-vote-refusal-branches into main 2026-11-10 14:02:29 +00:00
Heimdall
012d1cb926 Test: a second simultaneous close of a Standard motion must not throw
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>
2026-11-09 22:13:59 +00:00
Heimdall
0d7c3ec078 Mind intake: nonce prune is best-effort and never fails a valid request; test the five-lost-races contended branch; doc the retried reveal's phase judgement
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 20:55:03 +00:00
Heimdall
1c9cb9937e Signed Mind intake: verify before revealing state, dummy verify, issuedAt window and nonce pruning, per-source rate limit and body cap, one account per persona, DbUpdateException is replay only for the nonce
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 20:54:06 +00:00
Heimdall
8e6afc43ed Signed Mind intake for sealed motions: Ed25519 envelopes, registered public keys, nonce replay refusal, persona bound into the commitment
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 20:54:06 +00:00
Heimdall
55b7f79c13 Sealed ballot: retry a reveal that loses a RevealSeq race while the window is open, \z in hex patterns, doc the contended 409
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 20:47:23 +00:00
Heimdall
264233e08f Sealed ballot: freeze the reveal set at close (RevealSeq token), HTTP tests for replay/non-voter/late reveal/close, doc corrected on the ballot schema
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 20:40:54 +00:00
Heimdall
96da1b7a8c Sealed ballot: race-safe close, close sweep, endpoint tests, backup reader and auth findings in the doc
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 20:30:35 +00:00
Heimdall
d5a71eb639 Sealed-ballot motions: commit-reveal over typed gamecult.sealed_ballot.v0 documents
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-09 20:24:49 +00:00
7c563130e8 Merge pull request 'Make every cookie host-only: __Host- names, Secure, Path=/' (#11) from heimdall/host-only-cookies into main
Some checks failed
Publish Container / Build and publish Bifrost image (push) Failing after 14s
2026-11-09 11:48:27 +00:00
Heimdall
58fa80bc5b Make the attempt cookie's deletion Secure so a browser honours it
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>
2026-11-09 11:24:02 +00:00
Heimdall
7cfce18fcf Pin the vote page's two unpinned busy-refusal branches
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>
2026-11-08 21:28:41 +00:00
94606d04a6 Matrix mouth: re-read the brake before every join; pin the encoded presence path
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 16:24:17 +00:00
8938dbd739 Matrix mouth: review fixes (stale brake at the join, no receipt while braked, presence on the backlog path, guarded receipt log)
- 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>
2026-11-08 16:18:44 +00:00
111f7e6f1a Mouth tests: the no-wait probe fails fast instead of hanging
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 16:09:25 +00:00
b1b145ee39 Matrix mouth: post each Persona's m.read receipt on her ack, set her presence from her brake
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>
2026-11-08 16:04:50 +00:00
Heimdall
fd0a43e0e4 Image store: room directory must share the Persona directory's group; never create the Persona directory
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>
2026-11-08 09:29:59 +00:00
Heimdall
129b517f46 Image store: never chmod a room directory; refuse when setgid is not inherited
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>
2026-11-08 09:27:40 +00:00
Heimdall
a85942e858 Test: the streamed cap fails by assertion, not by timeout
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 09:21:46 +00:00
Heimdall
e23f7da2d5 Test: a failed image write leaves no temp file
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-08 09:19:32 +00:00
Heimdall
02275ee17b Matrix mouth: fetch and store chat images (cut A), ChatDelivery v3
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>
2026-11-08 09:18:55 +00:00
Heimdall
c757acf525 Make every cookie host-only: __Host- names, Secure, Path=/, no Domain
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>
2026-11-07 18:42:43 +00:00
Heimdall
6548ca8385 mouth: deliver media events as a typed line
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>
2026-11-03 18:09:43 +00:00
7adc7315a3 send gate: re-check the brake after the gate wait, before the PUT
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>
2026-11-03 10:37:22 +00:00
Meta Vens
2387cbc7f6 send gate tests: pin the head read's shape
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 11:06:39 +01:00
Meta Vens
981164173f send gate tests: arrival order is the gate's, not the call site's
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 11:02:47 +01:00
Meta Vens
2dd4bd863e persona matrix mouth: send gate tests, the captured #swarm head, v2 golden fixtures
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 11:02:07 +01:00
Meta Vens
a76c70cc39 persona matrix mouth: a v2 send is refused if the room moved past her basis; check and PUT are one step per room across every Persona
Removes the bare PUT; the one PUT now lives inside the room gate. v1 stays served unchecked through the same gate.
Wire: chat_send_request.v2 (basis) and chat_send_receipt.v2 (newer); RoomMoved is v2 only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 11:01:44 +01:00
Meta Vens
c29c5d1a4e persona matrix mouth: a delivery's context is read from Matrix, not remembered
Some checks failed
Publish Container / Build and publish Bifrost image (push) Has been cancelled
The per-room in-memory history map is gone: a restart emptied it and /sync resumed from the
cursor, so every room (a DM first) lost its past. When a delivery is built the mouth now reads
the room's timeline from the homeserver as that Persona (GET /messages dir=b, or the thread's
root plus /relations m.thread for a threaded message), keeps m.room.message from everyone
including her own, oldest first, ending before the delivered event. Newest lines are kept to a
16 KiB byte budget (each clipped to limits.contextBytes), at most the 20 lines the delivery
tuple carries. One fetch per room or thread per sync round. A failed fetch gives empty context
and a redacted log line; the delivery still goes out.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 05:10:57 +01:00
Meta Vens
98689b787d persona matrix mouth: each Persona's rooms are the shared MATRIX_ROOMS plus her own MATRIX_ROOMS_<SLUG>
Some checks failed
Publish Container / Build and publish Bifrost image (push) Has been cancelled
SLUG is the Persona slug upper-cased with "." and "-" turned into "_". Lists are
deduplicated; an absent variable gives shared rooms only; a malformed project alias,
or two Personas mapping to one variable, is refused at start. The send path's
UnknownRoom check already reads the mouth's own list, so another Persona's project
room is refused.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 04:12:31 +01:00
Meta Vens
b892c64f00 persona matrix mouth: one session per client on the many-peer RUDP server; every request answered on the connection it arrived on
Two Persona turns running at once no longer take each other's session. Requests run in order per client, not across clients.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 01:51:57 +01:00
Meta Vens
cc76f0f2d0 mouth-all-personas: one Matrix mouth serves every Persona; deliveries carry mentions and is_direct, refusals are typed; same-server DM invites accepted; the ring is deleted (Persona turns are the heartbeat's) (squash of eureka-body/mouth-all-personas 7b63031..5b628e2; Soul s1-s3)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 02:49:59 +01:00
Meta Vens
7b630312e1 persona-mouth: Matrix ear and mouth for Personas (brake-refusing sends, token only in the Authorization header, cursor after ack, one ring per idle window); delete the Discord posting actuator (squash of eureka-body/persona-mouth 1894f65..ab2bdc6; Soul s1)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-29 19:41:30 +01:00
Meta Vens
37c8fe1a72 Logout tests: pin the loop's own tick-failure catch now that subject faults are isolated
A pass that cannot read its subjects (a scope factory failing like a database outage) is logged
by exception type only and retried after the interval, never in a tight loop. The earlier loop
test reached this catch through a per-subject exception, which the isolation now absorbs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 01:57:31 +01:00
Meta Vens
ec66fba5ca Logout client: no auto-redirect, pin the redirect rule and the 10 s timeout
The named logout HttpClient's primary handler disables auto-redirect, so a 3xx from MAS is a
non-delivery retried like any non-2xx instead of being followed to a final 2xx that revokes.
Pinned through the real DI registration against a real loopback listener answering 302, and
the registered client's Timeout is pinned to exactly 10 s. The unexpected-exception test is
replaced by the isolation test (its fix is the next commit) and the cancellation test stands alone.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 01:50:53 +01:00
Meta Vens
51fe807b66 Logout loop test: the tick failure is logged by exception type only
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 01:03:02 +01:00
Meta Vens
af64868326 Logout tests: loop, log content, 2xx range, backoff reset, exception and cancellation paths
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 00:56:18 +01:00
Meta Vens
69b670e3af Fix logout test support compile errors
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 00:38:56 +01:00
Meta Vens
404dafdd40 OIDC back-channel logout: a reconciler ends the sessions of subjects who are not Active
OidcLogoutReconciler derives pending logouts from valid OpenIddict authorizations x
membership, POSTs one signed logout token per non-Active subject to MAS, and revokes
the authorizations only after a 2xx. Registered only when
Bifrost:Oidc:Clients:Mas:BackchannelLogoutUri is set. The active-member reading is
the endpoints' existing one, now shared.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 00:38:15 +01:00
Meta Vens
83daabe512 Bifrost as OIDC issuer for the Matrix chat (gamecult-chat cut bifrost-oidc-issuer, squash of 1ef289a..ec4acfa and abd12b3..)
One configured client (stale clients removed with their tokens and authorizations), Active membership re-checked at token and userinfo through IsStillActiveMemberAsync, PKCE required by the server option, OpenIddict.Server.AspNetCore + EntityFrameworkCore 7.7.1 with no vulnerable transitives. Soul s2, s3 and s4 hold; Low conformance gaps in gamecult-chat:follow_up:oidc-issuer-spec-conformance.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 04:57:16 +01:00
Meta Vens
d7d4b21c4b Commit NuGet lockfiles for the web and test projects
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 23:29:46 +02:00
Meta Vens
b2110f53fa Merge hands/heimdall-callback-signed
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-22 18:11:28 +02:00
Meta Vens
d743e4f363 Heimdall tests: an unreadable form with a genuine attempt cookie present is still no attempt
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 23:00:06 +02:00
Meta Vens
efb278a910 Heimdall tests: the no-attempt-field refusal carries a genuine code, so only the missing nonce can refuse it
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 22:57:26 +02:00
Meta Vens
d463336206 Heimdall tests: a genuine attempt cookie under a name that is no slot is no attempt
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 22:41:31 +02:00
Meta Vens
b54e8d5dc8 Heimdall tests: fix a string literal in the parallel-starts test
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 22:32:07 +02:00
Meta Vens
053272a0f3 Heimdall tests: re-point the cookie-ring tests to the slot cookie; a refusal with no matching attempt deletes nothing
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 22:31:25 +02:00
Meta Vens
15f96c408c Heimdall sign-in: attempt cookies live in a ring of two fixed slots, so the Cookie header is bounded by construction; pin the completion form's id
Start takes a free slot or overwrites the one expiring first; parallel starts carry the same request
cookies, choose the same slot and overwrite each other. Complete finds its attempt by scanning the
slots for the posted nonce; an overwritten attempt finds none and is refused like any other. The
per-request counting sweep, its byte budget and the nonce-named cookie are deleted.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 22:25:24 +02:00
Meta Vens
8186d6f8fa Heimdall tests: drop the exact-byte boundary test; the 5120-byte bound has a 3 KB margin, and no two attempt cookies can reach it
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 21:33:17 +02:00
Meta Vens
21f2c75c78 Heimdall tests: name the sums near the bound when the boundary pair is missing
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 21:32:31 +02:00