Desk: pin vectors to CultLib de8a062a (answerTooLarge) #45

Merged
heimdall merged 1 commit from heimdall/desk-pin-vectors-de8a062a into main 2026-11-11 02:16:29 +00:00
5 changed files with 260 additions and 5 deletions

View file

@ -4,7 +4,7 @@ using System.Text.Json.Serialization;
namespace Bifrost.Web.Features.Desk;
// The fork-desk documents Bifrost reads or writes, spelled as CultLib's contracts/cultnet/governance/fork-card.md
// (commit f6c9bf5f) spells them: snake_case keys, every key written (nil where absent), declared in the
// (commit de8a062a) spells them: snake_case keys, every key written (nil where absent), declared in the
// contract's key order. Bifrost authors only DeskRefusal and DeskUnavailable. AnswerRequest is the client's body;
// ForkCard, DeskAnswerDocument and AnswerResult are Huginn's documents, typed here so they pass through unchanged.

View file

@ -109,6 +109,54 @@ public sealed class DeskAnswerTests
}
}
[Fact]
public async Task A_stale_answer_over_an_oversize_fork_follows_the_vector_fresh_and_replayed()
{
var rows = DeskVectors.Rows("transport", "answerTooLarge").ToList();
Assert.Single(rows);
foreach (var row in rows)
{
var (factory, token) = await ArrangeAsync();
using (factory)
{
var request = row["request"]!;
var forkId = (string)row["fork_id"]!;
Assert.Equal(forkId, (string)request["fork_id"]!);
var stale = new AnswerResult(
DeskSchemas.AnswerResult,
new DeskAnswerDocument(
DeskSchemas.DeskAnswer, "n1", forkId, (string)row["outcome"]!, null, null, (string)row["code"]!,
(string)request["options_hash"]!, (string?)request["answer"]!["choice"], (string?)request["answer"]!["note"],
(string)request["answered_at"]!, "2026-11-12T19:02:05Z", false, DeskWebApplicationFactory.Alice, "Operator",
(string)request["request_id"]!),
null,
null);
factory.Door.AnswerScript = (_, _, _) => new DeskAnswerOutcome.Answered(stale);
factory.Door.ReadScript = (_, id) => new DeskReadOutcome.ForkTooLarge(id);
var body = DeskVectors.Bytes(request);
var target = DeskReplay.Target("answer", "gamecult-chat", null);
var fresh = await factory.SendAsync("POST", target, body, token);
var replay = await factory.SendAsync("POST", target, body, token);
foreach (var reply in new[] { fresh, replay })
{
Assert.Equal((int)row["httpStatus"]!, reply.Status);
var doc = JsonNode.Parse(reply.Body);
Assert.True(DeskVectors.SameRefusal(doc, row["refusal"], forkId, null), (string)row["name"]!);
Assert.Null(doc!["card"]);
}
Assert.Equal(fresh.Body, replay.Body);
// The door got the client's bytes untouched, stored nothing counted, and was asked for the card twice.
Assert.All(factory.Door.Calls.Where(c => c.Kind == "answer"), c => Assert.Equal(body, c.Body));
Assert.Equal(forkId, factory.Door.Calls.First(c => c.Kind == "read").ForkId);
Assert.False((bool)row["counted"]!);
Assert.NotEqual("Counted", stale.Answer.Outcome);
}
}
}
[Fact]
public async Task Card_read_failure_is_unavailable()
{

View file

@ -6,7 +6,7 @@ namespace Bifrost.Web.Tests;
public sealed class DeskVectorPinTests
{
[Fact]
public void Vectors_are_cultlib_f6c9bf5f()
public void Vectors_are_cultlib_de8a062a()
{
var sha = Convert.ToHexString(SHA256.HashData(File.ReadAllBytes(DeskVectors.Path))).ToLowerInvariant();

View file

@ -7702,6 +7702,213 @@
"fork_id": "gamecult-chat:question:q3"
}
],
"answerTooLarge": [
{
"name": "a_stale_answer_whose_card_reread_is_past_the_cap_is_fork_too_large_over_a_stored_stale_refusal",
"entries": [
{
"schemaVersion": "gamecult.fork.entry.v0",
"card": {
"schemaVersion": "gamecult.fork.card.v0",
"fork_id": "gamecult-chat:question:q3",
"title": "Fork 3",
"context": "Should a slot mark be shared?",
"options": [
{
"id": "yes",
"text": "Share it."
},
{
"id": "no",
"text": "Keep it local."
}
],
"recommendation": "no",
"depends": [
"gamecult-chat:question:file-zone-audience"
],
"leans": null,
"flag": null,
"raised_in": {
"kind": "CutSpec",
"id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4"
},
"deciders": [
"e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f"
],
"status": "open",
"ruling": null,
"ruled_from": [],
"order": 3,
"options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2"
},
"record": {
"schemaVersion": "gamecult.fork.record.v0",
"fork_id": "gamecult-chat:question:q3",
"options_hash": "ced892eb663c22e3d7ece602708043ee545f56b9d4e4421d7f5bfaa86c418ce2",
"counted_answer_id": null,
"answers": []
}
},
{
"schemaVersion": "gamecult.fork.entry.v0",
"card": {
"schemaVersion": "gamecult.fork.card.v0",
"fork_id": "gamecult-chat:question:q2",
"title": "Fork 2",
"context": "Should a slot mark be shared?",
"options": [
{
"id": "yes",
"text": "Share it."
},
{
"id": "no",
"text": "Keep it local."
}
],
"recommendation": "no",
"depends": [
"gamecult-chat:question:file-zone-audience"
],
"leans": null,
"flag": null,
"raised_in": {
"kind": "CutSpec",
"id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4"
},
"deciders": [
"e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f"
],
"status": "open",
"ruling": null,
"ruled_from": [],
"order": 2,
"options_hash": "5c7c2945e8e8689c6c928b11d7e0a033640b0c66afb7fcffb9e740c019d63e79"
},
"record": {
"schemaVersion": "gamecult.fork.record.v0",
"fork_id": "gamecult-chat:question:q2",
"options_hash": "5c7c2945e8e8689c6c928b11d7e0a033640b0c66afb7fcffb9e740c019d63e79",
"counted_answer_id": null,
"answers": []
}
},
{
"schemaVersion": "gamecult.fork.entry.v0",
"card": {
"schemaVersion": "gamecult.fork.card.v0",
"fork_id": "gamecult-chat:question:q1",
"title": "Fork 1",
"context": "Should a slot mark be shared?",
"options": [
{
"id": "yes",
"text": "Share it."
},
{
"id": "no",
"text": "Keep it local."
}
],
"recommendation": "no",
"depends": [
"gamecult-chat:question:file-zone-audience"
],
"leans": null,
"flag": null,
"raised_in": {
"kind": "CutSpec",
"id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4"
},
"deciders": [
"e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f"
],
"status": "open",
"ruling": null,
"ruled_from": [],
"order": 1,
"options_hash": "61959964bd214ea1b961554c43ec46a9faf156dd547cc50c8c16bbdcd55f223a"
},
"record": {
"schemaVersion": "gamecult.fork.record.v0",
"fork_id": "gamecult-chat:question:q1",
"options_hash": "61959964bd214ea1b961554c43ec46a9faf156dd547cc50c8c16bbdcd55f223a",
"counted_answer_id": null,
"answers": []
}
},
{
"schemaVersion": "gamecult.fork.entry.v0",
"card": {
"schemaVersion": "gamecult.fork.card.v0",
"fork_id": "gamecult-chat:question:q0",
"title": "Fork 0",
"context": "Should a slot mark be shared?",
"options": [
{
"id": "yes",
"text": "Share it."
},
{
"id": "no",
"text": "Keep it local."
}
],
"recommendation": "no",
"depends": [
"gamecult-chat:question:file-zone-audience"
],
"leans": null,
"flag": null,
"raised_in": {
"kind": "CutSpec",
"id": "cultlib-gaps:cut_spec:cut-slot-tolerance.r4"
},
"deciders": [
"e0d1c2b3-a495-4687-8879-6a5b4c3d2e1f"
],
"status": "open",
"ruling": null,
"ruled_from": [],
"order": 0,
"options_hash": "1777bd06ffafdf6e81bed5979f37193c4b4e7c3d2504f904e9131012f0767a9d"
},
"record": {
"schemaVersion": "gamecult.fork.record.v0",
"fork_id": "gamecult-chat:question:q0",
"options_hash": "1777bd06ffafdf6e81bed5979f37193c4b4e7c3d2504f904e9131012f0767a9d",
"counted_answer_id": null,
"answers": []
}
}
],
"request": {
"schemaVersion": "gamecult.fork.answer_request.v0",
"fork_id": "gamecult-chat:question:q3",
"options_hash": "14b8ffbdb61106dbc06f69f3fc8e2072af7a205d766e4cc040e89733013d54dc",
"answer": {
"choice": "yes",
"note": "Keep it local: a slot mark is the author's call, and sharing it is a separate act."
},
"retracted": false,
"retracts": null,
"answered_at": "2026-11-12T19:02:00Z",
"request_id": "0000a001-0000-4000-8000-00000000a001"
},
"refusal": {
"schemaVersion": "gamecult.fork.refusal.v0",
"code": "fork_too_large",
"error": "fork_too_large: fork gamecult-chat:question:q3 alone is past the response budget"
},
"refusalHex": "83AD736368656D6156657273696F6EB867616D6563756C742E666F726B2E7265667573616C2E7630A4636F6465AE666F726B5F746F6F5F6C61726765A56572726F72D950666F726B5F746F6F5F6C617267653A20666F726B2067616D6563756C742D636861743A7175657374696F6E3A713320616C6F6E6520697320706173742074686520726573706F6E736520627564676574",
"httpStatus": 422,
"outcome": "Refused",
"code": "stale_fork",
"counted": false,
"fork_id": "gamecult-chat:question:q3"
}
],
"doorFaults": [
{
"name": "read_door_answers_request_id_conflict_the_client_sees_unavailable",

View file

@ -3,12 +3,12 @@ using System.Text.Json.Nodes;
namespace Bifrost.Web.Tests.Support;
// CultLib's fork-card vectors, vendored at commit f6c9bf5f975177d2033578e9b51b4d21930b728a.
// CultLib's fork-card vectors, vendored at commit de8a062ab9d8c510c8b36e634fae8f108a570aff.
public static class DeskVectors
{
public const string SourceCommit = "f6c9bf5f975177d2033578e9b51b4d21930b728a";
public const string SourceCommit = "de8a062ab9d8c510c8b36e634fae8f108a570aff";
public const string FileSha256 = "cc15fc9e36e9c1f7d77b42279dc0f9a72c392adb251cc6ae3a59b79b7a004619";
public const string FileSha256 = "a7129eee00cb38c602481b0de7de0182f83122685b69ff441e3123adc3e0f774";
public static string Path => System.IO.Path.Combine(AppContext.BaseDirectory, "Fixtures", "fork-card-vectors.json");