Commit graph

448 commits

Author SHA1 Message Date
Heimdall
5ddfca9b55 MergeStatus page: the member's merges and undo by fresh proof of every moved login
Test files: the bearer list is kept apart from the surface it is checked against; wording tests use the Razor encoder.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 10:17:13 +00:00
Heimdall
ec28dffba1 MergeWording: the closed words of the merge pages
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 10:11:29 +00:00
Heimdall
8175e5ed99 Test: merge status page, notice, wording and undo by fresh proof (red; page, component and wording are skeletons)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 10:11:12 +00:00
Heimdall
24a880ad4c Test: a sign-in through a moved login lands on the survivor's account (saga rig opens to page tests)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 10:07:46 +00:00
Heimdall
e9a2e29c61 Test: first undefined attempt purpose is now 11 (9 and 10 are the merge proofs)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 10:04:45 +00:00
Heimdall
1924433af2 Complete merge-proof sign-ins into MergeProofs; refusals return to the starting page
Self redeems as the session's account; Other redeems a verified different account
that has a member account found by Heimdall account id. Every refusal precedes the
identity list, and the bearer is held only by MergeProofs.Put.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 10:00:20 +00:00
Heimdall
7bf284876a Test: merge-proof sign-ins complete into MergeProofs (red; completion cases are stubs)
Adds the two attempt purposes, their start methods, the four outcomes, the step's
return path and the MergeProofs registration; the completion switch cases throw
until the next commit.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 09:59:46 +00:00
Heimdall
3907259cb3 MergeProofs: in-memory per-actor holder of merge proofs
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 09:57:45 +00:00
Heimdall
5fbb673c55 Test: MergeProofs holder (red; type is a skeleton)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 09:56:49 +00:00
Heimdall
8175f6f61e Auth and member clients follow no redirect: one WithoutRedirects for all three
The mover's inline no-redirect handler becomes the shared extension; the
app-secret auth client and the member-bearer client use it too.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 09:56:49 +00:00
Heimdall
0599aedf08 Test: a 307 from Heimdall is not followed by the auth and member clients (red)
AddHeimdallClients gathers the three Heimdall client registrations so the test
exercises the registrations Program uses. The auth and member clients still
follow redirects here; the mover already does not.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 09:56:19 +00:00
2d9eb868bf Merge pull request 'Account merge: ledger, Heimdall mover client, crash-safe saga (merge design cuts A+B+saga)' (#86) from heimdall/account-merge into main 2026-11-14 09:42:49 +00:00
Heimdall
757ca2eeab Account merge saga: plan digest ignores the id of a membership not yet created, pin grace boundary, unique move ids and the confirm write gate, ProofSeal prints no bearer, commit the mutant probe
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 09:26:27 +00:00
Heimdall
a2172349ba Account merge saga: pin that a receipt mismatch stays the recorded answer across later sweeps
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 08:09:39 +00:00
Heimdall
b47a6557cb Move client: pin duplicate identityIds and over-length proofs as refused before any request
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 07:49:52 +00:00
Heimdall
41f4cc3853 Account merge saga: consumer tests through the real host and the fake mover, live-Postgres races, docs
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 06:48:43 +00:00
Heimdall
cdf64b2a10 Account merge saga: Confirm with consent record and sealed proofs, one crash-safe step function for the Heimdall move and reversal, one merge loop
AccountMergeSaga is the only caller of the move client and the only writer of Confirmed, HeimdallMoved, Failed, UndoRequested and HeimdallReversed; the
ledger keeps Committed, Final and Undone. Migration AddAccountMergeSaga adds the saga columns and the two move-id keys; its Down is roll-forward only.
AccountMergeSweeper becomes the one loop: saga advance, then finalize, every 30 s.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 06:48:43 +00:00
Heimdall
c62166c6ce Account merge ledger: pin the Stripe refusal for a folded account whose survivor is suspended
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 06:32:44 +00:00
Heimdall
f726101207 Account merge ledger: one write gate for every writer, held across the standing read and the save
WriteAsAsync takes a shared Postgres advisory lock on the account (the key the chain trigger locks exclusively) across the standing read and the writer's save; CommitAsync takes the exclusive lock before it reduces. Votes, appraisals, sealed commit and reveal, patron events (RecordSupportEventAsync routes and journals in the same save, so the manual Ledger door obeys it too) and the Heimdall sign-in pass through it; SupportTargetAsync, MayWriteAsync and JournalIntakeAsync are deleted. A folded or frozen account's sign-in writes no membership. A retry of an event stranded on the folded account journals nothing. Tests: Down refuses in every merge state, Stripe journal prior owner and undo, the lock racing a freeze and a commit on live Postgres. Docs say what the lock guarantees and what it does not.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 06:08:36 +00:00
Heimdall
5df08bd94a Move client: pin that invalid_request at 413 and 415 is Unknown, the rule is exactly 400
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 06:08:36 +00:00
Heimdall
41189e14d3 Merge mover client (heimdall/account-merge-mover-client b7b33bc) into account merge ledger 2026-11-14 05:55:57 +00:00
Heimdall
10fc9803d7 Account merge ledger: test the migration's duplicate current Patron snapshot dedupe
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 04:59:44 +00:00
Heimdall
537a477dc3 Account merge ledger: roll-forward-only Down guard, standing-at-write through one owner for every writer, one current Patron snapshot per membership, memberless Heimdall sign-in, intake and Stripe routing through one owner
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 04:58:26 +00:00
Heimdall
b7b33bc5db Move client: a 400 is RequestInvalid only with Heimdall's invalid_request body
An nginx HTML 400, an empty 400 or any other 400 is Unknown. The fake answers its schema 400 with Heimdall's error handler body.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 04:14:38 +00:00
Heimdall
aa348d3b43 Account merge ledger: link step refuses a frozen account (test), drop the redundant sync guard, accept a deadlock victim as the racing commit's loser
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 04:13:54 +00:00
Heimdall
1ffb8886c0 Move client: Refused only for Heimdall's coded move refusals, any pipeline exception is Unknown, ids end at \z
An uncoded 4xx (gateway 404, 408, 410, unknown code) is Unknown: Refused wipes sealed proofs. Unrecognized leaves the refusal enum.
The catch lists no types: only the caller's cancellation propagates. moveId and reversesMoveId patterns use \z in client and fake,
so a trailing line feed is refused as Heimdall's ajv refuses it; 64, 65 and newline are pinned.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 03:50:42 +00:00
Heimdall
f402eb13cc Account merge ledger: fix the first Soul pass (suspension carried into a memberless survivor, merged status terminal, standing at the desk door, linked accounts and write time, journalled grace intake, survivor weight on moved votes, one-hop alias, State token and database guards for overlapping merges, live-Postgres tests)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 03:37:48 +00:00
Heimdall
baa70bcef0 Move client: no redirects, one deadline over body reads, 64 KiB receipt cap, 409 move_id_conflict is Unknown
A 3xx is Unknown and never carries the secret or proofs off origin (registration owns the handler);
the HttpClient timeout now bounds the body read and any IOException or stall is Unknown; receipts
over 64 KiB are Unknown; the request's ToString hides its proofs; move_id_conflict is Unknown because
a committed move answers it when its proofs no longer verify, which keeps the sealed proofs resumable;
identity ids need not be GUIDs, proofs are bounded to 8192 and identityIds unique.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 03:13:14 +00:00
Heimdall
13ab2e3be7 Fake mover: port Heimdall 4fe367a's route schema and decision order
400 for every body the route schema rejects (oneOf toProof/reversesMoveId, duplicate or empty
identityIds, proof bounds), 404 identity_not_found for a source holding nothing, coverage-all-null
403 before the store, store order not_reversible/already_moved/not_found/not_proved/provider_conflict,
replay by signature without liveness (409 move_id_conflict for unsigned proofs), future-dated proofs
stale. Soul's P1-P4 are committed as FakeHeimdallMoverTests.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 03:13:14 +00:00
Heimdall
84ff2c4578 Account merge ledger: AccountMerge receipt, reductions, alias standing, undo and final sweep
AccountMergeLedger owns what a merge does to Bifrost records: PlanAsync (dry run, block codes),
CommitAsync (one save, total, idempotent), UndoLocalAsync (journal replay), FinalizeDueAsync
(AccountMergeSweeper) and StandingAsync/ResolveAsync. Votes and concept appraisals are voided by a
stamp that a model query filter hides from every tally. The actor accessor no longer ORs several
credentials under SingleOrDefault (a latent crash); a frozen or folded account resolves to no
account on every route, the OIDC authorize and chat refusal deny it, and patron intake follows the
alias. Migration AddAccountMerges is additive. No UI, no Heimdall call, nothing deployed.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 02:31:51 +00:00
Heimdall
fbb5853b1d Fake Heimdall mover and consumer-side move client tests
FakeHeimdall.Moves owns per-account logins, a bearer registry, revoked sessions and stored moves,
and answers the identity-moves route in decideMove's order (4fe367a): replay before proofs,
strict passkey coverage, freshness windows, first-receipt replay. Fault switches for status,
apply-then-fail and down.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 02:27:38 +00:00
Heimdall
b54cf27101 Heimdall identity-move client: closed Moved/Refused/Unknown outcome
POST /v1/apps/bifrost/identity-moves under the app secret (HeimdallAuthService.ConfigureClient,
same header redaction), 15 s timeout, request validated to the contract's closed body before any
I/O, 200 trusted only as a whole receipt answering this request. HeimdallRefusal gains ReadAsync
(code plus safe proof side and provider); CodeAsync delegates to it.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 02:27:38 +00:00
Heimdall
ee700d4931 Test: snapshot has no drift from the design-time model (passes at base 425af74)
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 02:20:25 +00:00
425af74500 Passkeys: WebAuthn enrolment under a fresh sign-in, 48 h cooling-off, assertion proof at unlink (#85)
Delta Soul clean at 73f97ed (no blocking findings; one Low: challenge check and spend not atomic).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-14 00:37:56 +00:00
c20947af1c Merge pull request 'Linked accounts page: list, link, unlink with fresh proof (merge design step 2)' (#84) from heimdall/linked-accounts into main 2026-11-13 20:24:16 +00:00
Heimdall
18f1ec6aca Linked accounts: drop the list after an unlink with an uncertain end; Google never mints; docs
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 20:23:54 +00:00
Heimdall
8e65f56b0c Linked accounts: never guess a proof provider; require stepUpRequested; no-store on completion
With no current list the proof goes only through a provider the member names, never the
session's sign-in provider: a sign-in through an unlinked provider makes Heimdall mint an
orphan account holding that login. Link buttons need a current list. The unlink completion
must say a step-up was requested. POST /auth/heimdall/complete answers no-store. The fake
Heimdall now models orphan minting, derives step_up_at from the start's stepUp flag, and
enforces the app secret, sign_in-only, unsupported-provider and 600 s rules.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 20:08:07 +00:00
Heimdall
b65bec65ce Linked accounts: docs, README line, proof-provider and no-store tests
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 19:43:59 +00:00
Heimdall
59ecaa606b Linked accounts page: list, link any admitted provider, unlink with step-up proof
A member sees their Heimdall logins, links Discord, Patreon or Google, and unlinks one
with a fresh Google step-up sign-in. Bifrost keeps no Heimdall token: each action is a
fresh Heimdall sign-in started by Bifrost whose bearer lives inside the completion
request. The list is kept five minutes in this process as non-secret fields only.

The Status page's Google-only link button and handler are cut; the Linked accounts page
owns link starts. HeimdallStartRequest gains stepUp. Heimdall's member routes use a
client without the app secret.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 19:35:40 +00:00
9a716cfd7e Merge pull request 'Feedback receiver design (records, session check, delivery, erase redaction)' (#10) from heimdall/feedback-receiver into main 2026-11-13 03:28:42 +00:00
ae0037c159 Merge pull request 'Guest accounts design (role field, invite, grants, leave and erase)' (#9) from heimdall/bifrost-guest-accounts into main 2026-11-13 03:28:41 +00:00
Heimdall
96665a778b docs(feedback-receiver): drop stale draft marker; steward merges the design
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-13 03:28:33 +00:00
Heimdall
3b0a614ee9 docs(feedback-receiver): steward rulings: Ratatoskr client (2a, shared client, own audience) and explicit short token lifetimes
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-12 22:47:05 +00:00
Heimdall
d29f8014f9 docs(guest-accounts): steward rulings: stored Role, form-posted invite code (no token in a URL), provider lists narrowed to the intersection, no collector member_id for guests
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-12 22:46:53 +00:00
Heimdall
25ce117f16 docs(feedback-receiver): re-read references at main e58e953
Docs only. Mouth line numbers (shifted by about 100 lines of mouth code), bridge.md, packages.lock.json refs refreshed; Heimdall PR #6 is merged.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-12 22:44:54 +00:00
Heimdall
f04b20feed docs(guest-accounts): re-read references at main e58e953; OIDC chat tier is already any non-Suspended account, owed change is Left/Erased
Docs only. Refreshes stale file and line references, drops the claim that OIDC must move to Status == Active (main already admits any non-Suspended account with a live training-notice receipt), names the new IsActiveMember callers, and updates the provider-list and Heimdall PR #6 notes.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-12 22:44:53 +00:00
e58e9531df Merge pull request 'Refuse production startup when the Heimdall app secret is blank' (#83) from heimdall/appsecret-empty-refusal into main 2026-11-12 19:27:58 +00:00
Heimdall
1771f15789 Soul fixes: deploy doc says production refuses a blank secret; name the echo test for what it checks
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-12 19:27:57 +00:00
Heimdall
1739d5552a Refuse production startup when the Heimdall app secret is blank
A missing secret silently switched Discord and Patreon member sign-in off.
Outside Development and Testing the validator now fails naming
Heimdall:AppSecretFile and Heimdall:AppSecret, never a value.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-12 19:23:43 +00:00
920ecb1fbe Merge pull request 'Google link: pin the four test gaps, one slot owner, drop IsSignInProvider' (#82) from heimdall/google-link-test-gaps into main 2026-11-12 17:02:18 +00:00