Commit graph

190 commits

Author SHA1 Message Date
Meta Vens
cf74019194 Mill votes: the very-busy refusal offers sign-in to a voter who is not signed in
Accounts have their own table, so signing in works while the cast or mint
table is full. A signed-in voter is not told to sign in. Pinned through the
full pipeline at a small cap, for both audiences.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 20:23:59 +02:00
Meta Vens
a9b9b29bf9 Pin the account table's own cap
The cap-not-wired-accounts hand mutant survived: no test filled the account table.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 19:58:09 +02:00
Meta Vens
912d083894 Fix a string literal broken across lines in the table-full test
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 19:17:27 +02:00
Meta Vens
436a02864f Vote budgets: at the cap a new key evicts the least recently used key with permits left
Ruling budget-table-evict-oldest. FixedWindowBudget keeps two linked lists (window start, last use among keys
with permits left) so eviction and expiry are O(1); a spent key is never evicted, and a table full of spent keys
refuses a new key as TableFull with the true wait. Accounts get their own cast table. The vote page checks the
subject before spending any key, and the refusal names its reason: cast budget, mint budget, or a busy table.
Not verified by a build yet at this commit.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 19:16:30 +02:00
Meta Vens
8f57dc89f0 Pin the no-client-address bucket of the vote budgets
Kills the Stryker survivor on the null-client key: a request with no address shares one
bucket and does not throw. Builds with a8c3ecf; test added after the main verify job.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 17:38:56 +02:00
Meta Vens
a8c3ecf495 Vote budgets: monotonic windows, a key cap, 30 mints an hour and a readable 429
FixedWindowBudget times windows on the clock's monotonic timestamps so a wall-clock
step cannot stretch or reset one, expires windows from a start-ordered queue instead
of a periodic sweep, and holds at most MaxTrackedKeys keys: a new key at the cap is
denied with the true wait and a held key is never evicted. MintPermits is 30 per hour
per the operator ruling, and a 429 now carries a plain-text reason; a refused new
device is told it can sign in. Not yet built or tested at this commit.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-11 17:32:01 +02:00
Meta Vens
2c8384e44a Pin that a signed-in retract spends the account budget
Hand mutant retract-key-ignores-signed-in-voter survived the first table.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 09:46:45 +02:00
Meta Vens
9fcef927fd Pin the budget sweep and Retry-After rounding; drop a redundant Math.Max
Survivors of the first Stryker pass on FixedWindowBudget: the sweep dropping
live windows, sweeping every call, and the exact window boundary (masked by
the sweep in the first test).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 09:35:50 +02:00
Meta Vens
7f375ecf8f Pin the empty Origin branch with a server-built request
HttpClient drops an empty header value, so the first version of the test
never sent the header; the request is now built on the TestServer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 09:02:01 +02:00
Meta Vens
fbf6e43955 Concept vote rate limits: cast and mint budgets per account or network prefix
One FixedWindowBudget (injectable clock) owned by ConceptVoteRateLimits;
the vote page 429s with Retry-After before any row or cookie. Prefix
lengths and budgets are validated in every environment. Pins the empty
Origin branch of CastRequestGuard.

Unverified; not yet built.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 09:00:23 +02:00
Meta Vens
68214ced30 Merge hands/mill-votes-cut-5: the vote page, the device-cookie voter key and cookieless CSRF
Held by concept-mill-votes:verdict:cut-5.s2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:54:41 +02:00
Meta Vens
526102ec8a ConceptVotePageTests: pin a repeated Origin header and the exact client-error codes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:26:21 +02:00
Meta Vens
75d2cedaa2 ConceptVotePageTests: drop the empty-Origin case (HttpClient never sends an empty header)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:18:06 +02:00
Meta Vens
d3a7611f2a Vote page: explicit no-store (the antiforgery write no longer supplies it), handler must match the request, empty Origin counts as present
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:15:13 +02:00
Meta Vens
30ba2af3c1 ConceptVotePageTests: close the Visitor class correctly
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:13:42 +02:00
Meta Vens
6554a1b017 ConceptVotePageTests: fix a stray brace
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:11:50 +02:00
Meta Vens
59d5dbd068 Vote page: gate in the page filter's execution stage (the selected stage has no Result)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:11:01 +02:00
Meta Vens
e0b3b1a38a Vote page: cookieless CSRF (Origin / Sec-Fetch-Site) and a defined error for handler-less requests
Drops antiforgery from the anonymous vote flow: no antiforgery cookie, no hidden token.
CastRequestGuard is the one decider; VoteModel.OnPageHandlerSelected is the one gate and
answers 4xx when no handler is selected (was a NullReferenceException 500).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-11 00:09:25 +02:00
Meta Vens
0c6f9216b6 Merge origin/main into hands/mill-votes-cut-5 2026-10-11 00:06:51 +02:00
Meta Vens
72741198fa Merge hands/mill-votes-cut-3: concept subject registration from the Mill manifest
Held by concept-mill-votes:verdict:cut-3.s2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:42:02 +02:00
Meta Vens
c9e0b7b0e1 Registry tests: pin the order of already-retired entries
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:26:43 +02:00
Meta Vens
747183078a Registry tests: build the over-bound manifest as an array
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:08:11 +02:00
Meta Vens
9c4124dd9f Registry tests: spell the separator and format characters as escapes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:04:00 +02:00
Meta Vens
0bb3b2f76c Registry: typed refusals for every input failure; strict manifest reader; retire rerun converges
Empty, NUL and unreadable paths, malformed UTF-8 and lone surrogates refuse
with the command's exit 1. The manifest is bounded (1 MiB, 1000 ships) and
duplicate JSON properties are refused. Titles refuse format, line and
paragraph separator characters and edge whitespace. Naming an already
retired key is a recorded no-op, so a rerun converges.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:01:40 +02:00
Meta Vens
f7ff76daa4 ConceptVotePageTests: find the visitor's own row by account-less key, drop the empty-tag assertion
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 22:54:03 +02:00
Meta Vens
e05e1bed81 ConceptVotePageTests: attach the log capture through ConfigureServices
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 22:51:24 +02:00
Meta Vens
f5a1f387ca Mill vote page: anonymous cast, change and retract, cookie minted only by a cast
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 22:48:04 +02:00
Meta Vens
af3b712b26 VoterKeyResolver: the one reader and writer of the bifrost_voter device cookie
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 22:48:04 +02:00
Meta Vens
a35119362a Merge hands/mill-votes-cut-4: public tally export with one-origin CORS
Held by concept-mill-votes:verdict:cut-4.s2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 22:42:42 +02:00
Meta Vens
11df901cb6 Registry tests: pin the refusal message shape
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 22:35:46 +02:00
Meta Vens
dcc3425520 Tally export Vary: plain append, no dedupe
The dedupe branch only added equivalent mutants; a repeated Origin token is
harmless.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 22:19:52 +02:00
Meta Vens
464da6bd50 Tally export: every response under the route varies by Origin
UseConceptTallyCors replaces UseCors and appends Vary: Origin to every
response under the export route, preflights and 404s included, keeping any
Vary already set. A single-origin ASP.NET policy never adds it itself.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:46:51 +02:00
Meta Vens
a43f90b48d Registry: one throw for a missing or non-array ships; tests pin refusal codes, title bound and plan order
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 21:39:04 +02:00
Meta Vens
46d92200e5 Registry: parse and read through small helpers so definite assignment survives mutation
Stryker flagged ParseManifest, ReadString and RunAsync as compile errors in safe
mode (out vars and try-assigned locals), leaving them unmutated. Behaviour unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:34:03 +02:00
Meta Vens
624cfd1567 Concept tally export and its CORS policy move out of Program.cs
One owner for the route and the policy, so Stryker can mutate them: it
flags every mutant of Program.cs as a compile error (safe mode), which
left the export untested by mutation. Program.cs keeps UseCors and the
two registration calls.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:20:13 +02:00
Meta Vens
cc00c64c9e Startup validation: an allowed origin with user info is refused
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:07:47 +02:00
Meta Vens
34325e0efd Registry tests: the helper voter's device id no longer embeds the subject key
Commit b17436c does not pass: the forged-key test failed on an invalid device id.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 20:00:04 +02:00
Meta Vens
d13b808471 Public concept tally export with one-origin CORS
GET /public/concepts/{collection}/tallies returns per-live-subject up, down and
member counts only, anonymous, Cache-Control public max-age=60. CORS policy
ConceptTallies is built from ConceptVotes:AllowedOrigins, GET only, no
credentials, required on this route alone. Production startup refuses an
allowed origin that is not an exact https origin.

Not yet built or tested when committed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 19:59:51 +02:00
Meta Vens
b17436ce4f Concept subject registry and concepts plan|apply host command
ConceptSubjectRegistry is the only writer of ConceptSubject. ConceptKey is the
one key grammar (anchored with \z), shared with ConceptAppraisalService.
Not yet built or tested at this commit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 19:58:04 +02:00
Meta Vens
95ac1f84e6 Merge hands/mill-votes-cut-2: appraisal votes, voter identity and network tag
Held by concept-mill-votes:verdict:cut-2.s2.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 19:53:50 +02:00
Meta Vens
bd06ef0c74 ConceptAppraisalServiceTests: a recast through a fresh context persists the changed stance 2026-10-10 09:56:52 +02:00
Meta Vens
eca71f589c Startup validation: tag secret minimum length and prefix lengths inside the address family, naming keys not values
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 09:56:34 +02:00
Meta Vens
162d7b1f0d Purge audit: Detail carries window and count, never the network tag the member dashboard would render
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 09:56:08 +02:00
Meta Vens
abb0986508 VoterIdentity: anchor the device id grammar at \z so a trailing newline or control char is no second identity
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 09:55:26 +02:00
Meta Vens
d171713ab1 Concept vote tests: close the mutation survivors in view, purge window, slug and prefix masking
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 08:42:55 +02:00
Meta Vens
214f555781 ConceptAppraisalServiceTests: build actors fully before saving; order after ToList
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 07:59:42 +02:00
Meta Vens
270ab95cc7 Migration AddConceptVotes, generated by dotnet-ef 8.0.0
The snapshot is regenerated; EF also reordered two pre-existing properties
(BridgeAction.TargetSurface, one ProjectId) into its canonical order.
Migration discovery now lists thirteen ids.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 06:39:59 +02:00
Meta Vens
3c135b8e67 Concept votes: subjects, appraisals, voter identity, network tag and their one writer
ConceptAppraisalService is the only appraisal writer; tallies are derived from
rows. VoterIdentity.For decides voter key and class; NetworkPrefix is the only
prefix computation (HMAC tag). NetworkTagSecret is required in production.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 06:31:43 +02:00
Meta Vens
67072b2892 Merge hands/mill-votes-cut-1: loopback-only forwarded headers, pinned key ring
concept-mill-votes:cut_report:cut-1.h1 and h2, held by Soul
(concept-mill-votes:verdict:cut-1.s2). Production refuses to start without
DataProtection:KeysPath; the deploy (Cut 0b) must set it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 06:27:04 +02:00
Meta Vens
717e97bdcc Dockerfile: drop the forwarded-headers env switch; Program.cs is the one owner
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 05:58:35 +02:00