Fork card: short list pages and fork_too_large (422) for a first entry past the response budget
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
9d6ee72eb3
commit
01cc741278
6 changed files with 1058 additions and 5 deletions
|
|
@ -2692,6 +2692,16 @@
|
|||
"error": "request_invalid: the request does not satisfy its document"
|
||||
},
|
||||
"messagePackHex": "83AD736368656D6156657273696F6EB867616D6563756C742E666F726B2E7265667573616C2E7630A4636F6465AF726571756573745F696E76616C6964A56572726F72D93A726571756573745F696E76616C69643A20746865207265717565737420646F6573206E6F7420736174697366792069747320646F63756D656E74"
|
||||
},
|
||||
{
|
||||
"name": "fork_too_large",
|
||||
"httpStatus": 422,
|
||||
"message": {
|
||||
"schemaVersion": "gamecult.fork.refusal.v0",
|
||||
"code": "fork_too_large",
|
||||
"error": "fork_too_large: fork gamecult-chat:question:q3 (order 3) alone is past the response budget; read it by id"
|
||||
},
|
||||
"messagePackHex": "83AD736368656D6156657273696F6EB867616D6563756C742E666F726B2E7265667573616C2E7630A4636F6465AE666F726B5F746F6F5F6C61726765A56572726F72D969666F726B5F746F6F5F6C617267653A20666F726B2067616D6563756C742D636861743A7175657374696F6E3A713320286F7264657220332920616C6F6E6520697320706173742074686520726573706F6E7365206275646765743B2072656164206974206279206964"
|
||||
}
|
||||
],
|
||||
"unavailable": [
|
||||
|
|
@ -6561,6 +6571,940 @@
|
|||
]
|
||||
}
|
||||
],
|
||||
"listShortPages": [
|
||||
{
|
||||
"name": "a_short_page_resumes_below_its_last_entry",
|
||||
"ordinal": 9,
|
||||
"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": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"request": {
|
||||
"schemaVersion": "gamecult.fork.list_request.v0",
|
||||
"mind": "gamecult-chat",
|
||||
"status": null,
|
||||
"cursor": null,
|
||||
"limit": 3
|
||||
},
|
||||
"response": {
|
||||
"schemaVersion": "gamecult.fork.list.v0",
|
||||
"mind": "gamecult-chat",
|
||||
"stamp": "s9",
|
||||
"open_count": 4,
|
||||
"recorded_count": 0,
|
||||
"forks": [
|
||||
{
|
||||
"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": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"next_cursor": "c2"
|
||||
},
|
||||
"responseHex": "87AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E6C6973742E7630A46D696E64AD67616D6563756C742D63686174A57374616D70A27339AA6F70656E5F636F756E7404AE7265636F726465645F636F756E7400A5666F726B739283AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7133A57469746C65A6466F726B2033A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657203AC6F7074696F6E735F68617368D94063656438393265623636336332326533643765636536303237303830343365653534356635366239643465343432316437663562666161383663343138636532A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7133AC6F7074696F6E735F68617368D94063656438393265623636336332326533643765636536303237303830343365653534356635366239643465343432316437663562666161383663343138636532B1636F756E7465645F616E737765725F6964C0A7616E73776572739083AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7132A57469746C65A6466F726B2032A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657202AC6F7074696F6E735F68617368D94035633763323934356538653836383963366339323862313164376530613033333634306230633636616662376663666662396537343063303139643633653739A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7132AC6F7074696F6E735F68617368D94035633763323934356538653836383963366339323862313164376530613033333634306230633636616662376663666662396537343063303139643633653739B1636F756E7465645F616E737765725F6964C0A7616E737765727390AB6E6578745F637572736F72A26332"
|
||||
},
|
||||
{
|
||||
"request": {
|
||||
"schemaVersion": "gamecult.fork.list_request.v0",
|
||||
"mind": "gamecult-chat",
|
||||
"status": null,
|
||||
"cursor": "c2",
|
||||
"limit": 3
|
||||
},
|
||||
"response": {
|
||||
"schemaVersion": "gamecult.fork.list.v0",
|
||||
"mind": "gamecult-chat",
|
||||
"stamp": "s9",
|
||||
"open_count": 4,
|
||||
"recorded_count": 0,
|
||||
"forks": [
|
||||
{
|
||||
"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": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"next_cursor": null
|
||||
},
|
||||
"responseHex": "87AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E6C6973742E7630A46D696E64AD67616D6563756C742D63686174A57374616D70A27339AA6F70656E5F636F756E7404AE7265636F726465645F636F756E7400A5666F726B739283AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7131A57469746C65A6466F726B2031A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657201AC6F7074696F6E735F68617368D94036313935393936346264323134656131623936313535346334336563343661396661663135366464353437636335306338633136626264636435356632323361A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7131AC6F7074696F6E735F68617368D94036313935393936346264323134656131623936313535346334336563343661396661663135366464353437636335306338633136626264636435356632323361B1636F756E7465645F616E737765725F6964C0A7616E73776572739083AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7130A57469746C65A6466F726B2030A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657200AC6F7074696F6E735F68617368D94031373737626430366666616664663665383162656435393739663337313933633462346537633364323530346639303465393133313031326630373637613964A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7130AC6F7074696F6E735F68617368D94031373737626430366666616664663665383162656435393739663337313933633462346537633364323530346639303465393133313031326630373637613964B1636F756E7465645F616E737765725F6964C0A7616E737765727390AB6E6578745F637572736F72C0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "a_first_entry_past_the_budget_is_fork_too_large_and_the_client_continues_below_its_order",
|
||||
"ordinal": 9,
|
||||
"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": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"request": {
|
||||
"schemaVersion": "gamecult.fork.list_request.v0",
|
||||
"mind": "gamecult-chat",
|
||||
"status": null,
|
||||
"cursor": null,
|
||||
"limit": 3
|
||||
},
|
||||
"refusal": {
|
||||
"schemaVersion": "gamecult.fork.refusal.v0",
|
||||
"code": "fork_too_large",
|
||||
"error": "fork_too_large: fork gamecult-chat:question:q3 (order 3) alone is past the response budget; read it by id"
|
||||
},
|
||||
"refusalHex": "83AD736368656D6156657273696F6EB867616D6563756C742E666F726B2E7265667573616C2E7630A4636F6465AE666F726B5F746F6F5F6C61726765A56572726F72D969666F726B5F746F6F5F6C617267653A20666F726B2067616D6563756C742D636861743A7175657374696F6E3A713320286F7264657220332920616C6F6E6520697320706173742074686520726573706F6E7365206275646765743B2072656164206974206279206964",
|
||||
"httpStatus": 422,
|
||||
"fork_id": "gamecult-chat:question:q3",
|
||||
"order": 3
|
||||
},
|
||||
{
|
||||
"request": {
|
||||
"schemaVersion": "gamecult.fork.list_request.v0",
|
||||
"mind": "gamecult-chat",
|
||||
"status": null,
|
||||
"cursor": "c3",
|
||||
"limit": 3
|
||||
},
|
||||
"response": {
|
||||
"schemaVersion": "gamecult.fork.list.v0",
|
||||
"mind": "gamecult-chat",
|
||||
"stamp": "s9",
|
||||
"open_count": 4,
|
||||
"recorded_count": 0,
|
||||
"forks": [
|
||||
{
|
||||
"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": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"next_cursor": null
|
||||
},
|
||||
"responseHex": "87AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E6C6973742E7630A46D696E64AD67616D6563756C742D63686174A57374616D70A27339AA6F70656E5F636F756E7404AE7265636F726465645F636F756E7400A5666F726B739383AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7132A57469746C65A6466F726B2032A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657202AC6F7074696F6E735F68617368D94035633763323934356538653836383963366339323862313164376530613033333634306230633636616662376663666662396537343063303139643633653739A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7132AC6F7074696F6E735F68617368D94035633763323934356538653836383963366339323862313164376530613033333634306230633636616662376663666662396537343063303139643633653739B1636F756E7465645F616E737765725F6964C0A7616E73776572739083AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7131A57469746C65A6466F726B2031A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657201AC6F7074696F6E735F68617368D94036313935393936346264323134656131623936313535346334336563343661396661663135366464353437636335306338633136626264636435356632323361A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7131AC6F7074696F6E735F68617368D94036313935393936346264323134656131623936313535346334336563343661396661663135366464353437636335306338633136626264636435356632323361B1636F756E7465645F616E737765725F6964C0A7616E73776572739083AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7130A57469746C65A6466F726B2030A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657200AC6F7074696F6E735F68617368D94031373737626430366666616664663665383162656435393739663337313933633462346537633364323530346639303465393133313031326630373637613964A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7130AC6F7074696F6E735F68617368D94031373737626430366666616664663665383162656435393739663337313933633462346537633364323530346639303465393133313031326630373637613964B1636F756E7465645F616E737765725F6964C0A7616E737765727390AB6E6578745F637572736F72C0"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "fork_too_large_on_order_zero_leaves_nothing_below",
|
||||
"ordinal": 9,
|
||||
"entries": [
|
||||
{
|
||||
"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": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"steps": [
|
||||
{
|
||||
"request": {
|
||||
"schemaVersion": "gamecult.fork.list_request.v0",
|
||||
"mind": "gamecult-chat",
|
||||
"status": null,
|
||||
"cursor": null,
|
||||
"limit": 5
|
||||
},
|
||||
"response": {
|
||||
"schemaVersion": "gamecult.fork.list.v0",
|
||||
"mind": "gamecult-chat",
|
||||
"stamp": "s9",
|
||||
"open_count": 2,
|
||||
"recorded_count": 0,
|
||||
"forks": [
|
||||
{
|
||||
"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": []
|
||||
}
|
||||
}
|
||||
],
|
||||
"next_cursor": "c1"
|
||||
},
|
||||
"responseHex": "87AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E6C6973742E7630A46D696E64AD67616D6563756C742D63686174A57374616D70A27339AA6F70656E5F636F756E7402AE7265636F726465645F636F756E7400A5666F726B739183AD736368656D6156657273696F6EB667616D6563756C742E666F726B2E656E7472792E7630A463617264DE0010AD736368656D6156657273696F6EB567616D6563756C742E666F726B2E636172642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7131A57469746C65A6466F726B2031A7636F6E74657874BD53686F756C64206120736C6F74206D61726B206265207368617265643FA76F7074696F6E739282A26964A3796573A474657874A953686172652069742E82A26964A26E6FA474657874AE4B656570206974206C6F63616C2EAE7265636F6D6D656E646174696F6EA26E6FA7646570656E647391D92967616D6563756C742D636861743A7175657374696F6E3A66696C652D7A6F6E652D61756469656E6365A56C65616E73C0A4666C6167C0A97261697365645F696E82A46B696E64A743757453706563A26964D92B63756C746C69622D676170733A6375745F737065633A6375742D736C6F742D746F6C6572616E63652E7234A8646563696465727391D92465306431633262332D613439352D343638372D383837392D366135623463336432653166A6737461747573A46F70656EA672756C696E67C0AA72756C65645F66726F6D90A56F7264657201AC6F7074696F6E735F68617368D94036313935393936346264323134656131623936313535346334336563343661396661663135366464353437636335306338633136626264636435356632323361A67265636F726485AD736368656D6156657273696F6EB767616D6563756C742E666F726B2E7265636F72642E7630A7666F726B5F6964B967616D6563756C742D636861743A7175657374696F6E3A7131AC6F7074696F6E735F68617368D94036313935393936346264323134656131623936313535346334336563343661396661663135366464353437636335306338633136626264636435356632323361B1636F756E7465645F616E737765725F6964C0A7616E737765727390AB6E6578745F637572736F72A26331"
|
||||
},
|
||||
{
|
||||
"request": {
|
||||
"schemaVersion": "gamecult.fork.list_request.v0",
|
||||
"mind": "gamecult-chat",
|
||||
"status": null,
|
||||
"cursor": "c1",
|
||||
"limit": 5
|
||||
},
|
||||
"refusal": {
|
||||
"schemaVersion": "gamecult.fork.refusal.v0",
|
||||
"code": "fork_too_large",
|
||||
"error": "fork_too_large: fork gamecult-chat:question:q0 (order 0) alone is past the response budget; read it by id"
|
||||
},
|
||||
"refusalHex": "83AD736368656D6156657273696F6EB867616D6563756C742E666F726B2E7265667573616C2E7630A4636F6465AE666F726B5F746F6F5F6C61726765A56572726F72D969666F726B5F746F6F5F6C617267653A20666F726B2067616D6563756C742D636861743A7175657374696F6E3A713020286F7264657220302920616C6F6E6520697320706173742074686520726573706F6E7365206275646765743B2072656164206974206279206964",
|
||||
"httpStatus": 422,
|
||||
"fork_id": "gamecult-chat:question:q0",
|
||||
"order": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"mindRoutes": {
|
||||
"minds": {
|
||||
"gamecult-chat": "huginn-gamecult-chat.sock",
|
||||
|
|
|
|||
|
|
@ -118,15 +118,15 @@ Source: the desk's client needs (Ratatoskr PR #24 section 12, `ratatoskr-desk-sp
|
|||
|
||||
Calls 1 and 2 are enough for a read-only desk. Every response is the pinned MessagePack document or its JSON form with the same keys (open choice 1 of `vote-api.md`: the media type is unsettled).
|
||||
|
||||
**Auth, in order, per route.** (1) A bearer, else `unauthenticated` (401). (2) The token's audience is `bifrost-desk`: **the Matrix Authentication Service token chat holds today is refused, and so is any `votes.*` token** (audience `bifrost-votes`), both as `unauthenticated`. (3) The scope the route needs, else `insufficient_scope` (403): `desk.read` for calls 1 and 2, `desk.answer` for call 3, and neither implies the other. (4) The account behind `sub` is an Active member, else `forbidden` (403), as in the vote API; scope is judged first. (5) For call 3 the body is then shaped (steps 1 and 2 above), so **a non-Active member who sends a bad body, including one naming `answerer` or `authority`, gets `forbidden`, not a body refusal**; a body naming either key at any depth is `body_forbidden_field` only for an Active member. (6) Only then is Huginn asked, so `unavailable` never hides an auth or body refusal. A `votes.*` token touches none of the three. Heimdall's file leaves the audience and the claims check open; `insufficient_scope`, `forbidden`, `request_invalid` (400, a list or read request that fails its document) and `request_id_conflict` are added to the door refusals.
|
||||
**Auth, in order, per route.** (1) A bearer, else `unauthenticated` (401). (2) The token's audience is `bifrost-desk`: **the Matrix Authentication Service token chat holds today is refused, and so is any `votes.*` token** (audience `bifrost-votes`), both as `unauthenticated`. (3) The scope the route needs, else `insufficient_scope` (403): `desk.read` for calls 1 and 2, `desk.answer` for call 3, and neither implies the other. (4) The account behind `sub` is an Active member, else `forbidden` (403), as in the vote API; scope is judged first. (5) For call 3 the body is then shaped (steps 1 and 2 above), so **a non-Active member who sends a bad body, including one naming `answerer` or `authority`, gets `forbidden`, not a body refusal**; a body naming either key at any depth is `body_forbidden_field` only for an Active member. (6) Only then is Huginn asked, so `unavailable` never hides an auth or body refusal. A `votes.*` token touches none of the three. Heimdall's file leaves the audience and the claims check open; `insufficient_scope`, `forbidden`, `request_invalid` (400, a list or read request that fails its document; an unknown key, `authority` included, is `request_invalid` here, whatever Huginn's own decode does with it) and `request_id_conflict` are added to the door refusals, and so is `fork_too_large` (422, list only; see List).
|
||||
|
||||
**List.** `list_request`: `{mind, status, cursor, limit}`. `mind` is the route's `{mind}`, a Mind id from the configured list. `status` is `open`, `recorded` or nil for both; `recorded` means a closed card. Cards come **newest first by `order`**. `order` is the card's unique rank per Mind: an integer from 0 that never changes once the card exists, later questions only appending (Huginn derives it from the Mind's questions sorted by admission ordinal, then key). A Mind never holds two cards of one `order`; a list or card set that does is refused, and the refusal names both fork ids. 0 is legal, but no cursor names it (`c0` fails the cursor pattern): a page ending at order 1 returns `c1`, and the next page returns the order-0 card alone with `next_cursor` nil. `limit` is 0 to 50; **`limit` 0 returns no entries, only the counts and the stamp**, which is what "N open" on the Mind's header needs. `cursor` is nil or `c<N>`: the cards whose `order` is below N; a client treats it as opaque and sends back the `next_cursor` it was given. `list`: `{mind, stamp, open_count, recorded_count, forks[entry], next_cursor}`. The counts cover every card the viewer may see, whatever `status` and `cursor` asked for. `stamp` is `s<N>`, N the receiver's last-seen Huginn admission ordinal for the Mind's desk documents: the same stamp means the same state, so a desk polls `limit` 0 and fetches bodies only when the stamp moves. `next_cursor` is nil on the last page. An `entry` is `{card, record}`: the card and its `record` (the answers with their outcomes and the derived `counted_answer_id`).
|
||||
**List.** `list_request`: `{mind, status, cursor, limit}`. `mind` is the route's `{mind}`, a Mind id from the configured list. `status` is `open`, `recorded` or nil for both; `recorded` means a closed card. Cards come **newest first by `order`**. `order` is the card's unique rank per Mind: an integer from 0 that never changes once the card exists, later questions only appending (Huginn derives it from the Mind's questions sorted by admission ordinal, then key). A Mind never holds two cards of one `order`; a list or card set that does is refused, and the refusal names both fork ids. 0 is legal, but no cursor names it (`c0` fails the cursor pattern): a page ending at order 1 returns `c1`, and the next page returns the order-0 card alone with `next_cursor` nil. `limit` is 0 to 50; **`limit` 0 returns no entries, only the counts and the stamp**, which is what "N open" on the Mind's header needs. `cursor` is nil or `c<N>`: the cards whose `order` is below N; a client treats it as opaque and sends back the `next_cursor` it was given. `list`: `{mind, stamp, open_count, recorded_count, forks[entry], next_cursor}`. The counts cover every card the viewer may see, whatever `status` and `cursor` asked for. `stamp` is `s<N>`, N the receiver's last-seen Huginn admission ordinal for the Mind's desk documents: the same stamp means the same state, so a desk polls `limit` 0 and fetches bodies only when the stamp moves. `next_cursor` is nil on the last page. **A page may hold fewer than `limit` entries** when the next entry would push the encoded response past the receiver's response budget (Huginn: 65536 bytes); it then holds at least one, `next_cursor` is `c<order of the last entry returned>` so the next page resumes below it, and the counts and the stamp are those of the full list, unchanged. **`fork_too_large`** (HTTP 422, list only) refuses a page whose **first** entry alone is past the budget: 422 because the request is well formed and authorised but names a resource the list cannot carry, and 413 is for a request body that is too large, which this is not; retrying returns the same refusal. Its `error` names the fork id and its `order` (`fork_too_large: fork <fork_id> (order <n>) alone is past the response budget; read it by id`). The client reads that fork with the read route, which always returns the whole entry and never this refusal, and continues the list with cursor `c<n>`, the cards strictly below that order; `c<n>` is valid only when n is at least 1, so for order 0 there is nothing below and the client has reached the end. `limit` 0 returns no entries and never refuses. (Ruled by Persona, 2026-11-11.) An `entry` is `{card, record}`: the card and its `record` (the answers with their outcomes and the derived `counted_answer_id`).
|
||||
|
||||
**Read.** `read_request` is `{mind, fork_id}`; the response is the `entry` for one fork of that Mind. Its `card.options_hash` is **computed by the server** from the five shown members as in "Options hash" and the client recomputes it from what it rendered; a card whose hash is not the hash of its members is no card (negative vector `card_hash_wrong`). The record's `fork_id` and `options_hash` are the card's, and `counted_answer_id` is derived, not copied (negative vectors for each). Every `desk.read` holder sees every fork's card and outcome in a configured Mind; only a decider's notes depend on the caller, going to callers in the admin-set `deciders` list, which Bifrost enforces at the door (ruling `desk-read-visibility`, option (c)); `fork_unknown` is only for a fork id that does not exist under the route's Mind (an unknown Mind answers the same, M2), so no fork is ever hidden by it, and a caller without `desk.read` is refused by the auth order above (`insufficient_scope`) before any lookup. **Bifrost applies visibility at the door; Huginn returns the whole record to the door.** Every `desk.read` holder sees cards and outcomes; notes (a decider's own words) only go to callers in the admin-set `deciders` list. The read and list shapes are unchanged and carry no audience field (`eureka-substrate:question:desk-read-visibility`, Heimdall's option (c)). A stripped read serves a decider's `note` as `note: null`, never omitted (map-mode: every key is written); `desk_answer.note` is already nullable in the schema, so a served record validates as it stands.
|
||||
|
||||
**Answer.** The body is the `answer_request` and nothing else a client may set: `fork_id`, `options_hash`, `answer`, `answered_at`, `request_id` (plus `retracted` and `retracts`, written `false` and nil unless the call retracts). The response is the stored outcome: `Counted`, `Revision`, `Retraction`, or `Refused` with its code, and the **`counted_answer_id`** the fork has once this call is applied (`n<seq>` or nil; derived as in "The counted answer is derived", the same value the `record` projection carries, so the desk needs no second read). A replay returns the stored outcome with the `counted_answer_id` current at the retry, not the one it had when first stored. **A `stale_fork` refusal carries the current card** in `answer_result.card`, so the desk can show the new fork without another read. Nothing is stored for a step 1 or 2 refusal, an unknown fork (or an unknown Mind, or a fork another Mind holds) or a `request_id_conflict`. `unavailable` promises nothing either way.
|
||||
|
||||
**Vectors (`transport` in `fork-card-vectors.json`).** `listRequest`, `readRequest` and `entry` (valid and invalid, with bytes); `listInvalid` (including `two_cards_share_one_order`, whose `refusal` is the exact text both judges must produce: `order 4 is shared by <first fork id> and <second fork id>`); `listScenarios` (six scenarios: over one Mind's five cards, paged open-only, recorded-only, all and `limit` 0; an empty Mind; and `order_zero_is_legal_but_no_cursor_names_it`, four cards of orders 3 to 0 paged by 3, whose first page ends at order 1 with `c1` and whose second page is the order-0 card with `next_cursor` nil; each response with its bytes, the cursor of page two being page one's `next_cursor`); `authCases` (twenty-two cases: the MAS token, three `votes.*` tokens, a desk token with the other route's scope, a non-Active member on each route, and Huginn down on each route, with auth and membership judged first). Both suites replay the list requests through a reference judge and the auth cases through another. `mindRoutes` (a configured list of two Minds with their sockets and forks; 57 cases over the three routes: bad and percent-encoded segments, unknown Mind against unknown fork with `sameResponse` pairs of equal bytes, the POST on a Mind that does not hold the fork, scopes that are not per Mind, and auth judged before the Mind lookup) is replayed by both suites through a reference judge that reports the refusal, the socket it would open and the Mind that stored an answer. Top-level `unavailable` pins the 503 document. Scenarios pin: an `unavailable` where Huginn never had the request, retried into a `Counted` answer; an `unavailable` where Huginn had stored it, whose retry returns the stored outcome and adds no Revision; a replayed `Refused` with its card; `request_id_conflict`; the same id from another account; and a non-Active member with a bad body, with the door down, getting `forbidden` first. A bad body is refused before the door is asked.
|
||||
**Vectors (`transport` in `fork-card-vectors.json`).** `listRequest`, `readRequest` and `entry` (valid and invalid, with bytes); `listInvalid` (including `two_cards_share_one_order`, whose `refusal` is the exact text both judges must produce: `order 4 is shared by <first fork id> and <second fork id>`); `listScenarios` (six scenarios: over one Mind's five cards, paged open-only, recorded-only, all and `limit` 0; an empty Mind; and `order_zero_is_legal_but_no_cursor_names_it`, four cards of orders 3 to 0 paged by 3, whose first page ends at order 1 with `c1` and whose second page is the order-0 card with `next_cursor` nil; each response with its bytes, the cursor of page two being page one's `next_cursor`); `listShortPages` (three scenarios: a page that stops short and resumes below its last entry; `fork_too_large` on a first entry of order 3, continued with `c3`; and a short page ending at order 1 followed by `fork_too_large` on order 0, where the client is at the end; each step carries its bytes, and the receiver's budget is in no vector, so the judges check a short page as a nonempty strict prefix of the unbudgeted page and a refusal as naming the entry the page would open with); `authCases` (twenty-two cases: the MAS token, three `votes.*` tokens, a desk token with the other route's scope, a non-Active member on each route, and Huginn down on each route, with auth and membership judged first). Both suites replay the list requests through a reference judge and the auth cases through another. `mindRoutes` (a configured list of two Minds with their sockets and forks; 57 cases over the three routes: bad and percent-encoded segments, unknown Mind against unknown fork with `sameResponse` pairs of equal bytes, the POST on a Mind that does not hold the fork, scopes that are not per Mind, and auth judged before the Mind lookup) is replayed by both suites through a reference judge that reports the refusal, the socket it would open and the Mind that stored an answer. Top-level `unavailable` pins the 503 document. Scenarios pin: an `unavailable` where Huginn never had the request, retried into a `Counted` answer; an `unavailable` where Huginn had stored it, whose retry returns the stored outcome and adds no Revision; a replayed `Refused` with its card; `request_id_conflict`; the same id from another account; and a non-Active member with a bad body, with the door down, getting `forbidden` first. A bad body is refused before the door is asked.
|
||||
|
||||
**Bifrost must still build:** all three routes; the `bifrost-desk` audience and the `desk.read` and `desk.answer` scopes on the OIDC token it issues to chat; the configured Mind list and its sockets (M1); the projection of cards and records from Huginn, the stamp, the cursor and the counts; the `DeskAnswer` call on the second door and the pass-through of its outcome; the decider list, sent with every answer, read and list request from the admin-set list only (M5); the `unavailable` answer. **Huginn must still build** the second door's wire, the per-call decider list it receives with each answer, read and list request, keeping no copy (M5), and the read side the projection uses (Eureka's cut spec, after her record PR). **The desk may build today** against the vectors: nothing in them needs a live service.
|
||||
|
||||
|
|
|
|||
|
|
@ -714,6 +714,7 @@
|
|||
"body_unknown_field",
|
||||
"body_invalid",
|
||||
"fork_unknown",
|
||||
"fork_too_large",
|
||||
"insufficient_scope",
|
||||
"forbidden",
|
||||
"request_invalid",
|
||||
|
|
|
|||
|
|
@ -504,6 +504,46 @@ test("fork transport: order 0 is legal, no cursor names it, and the page ending
|
|||
assert.equal(check(forkSchema, "cursor", "c0"), false);
|
||||
});
|
||||
|
||||
test("fork transport: a page may stop short of limit, and a first entry past the budget is fork_too_large (422) continued below its order", () => {
|
||||
const sc = fork.transport.listShortPages;
|
||||
assert.deepEqual(sc.map((x: any) => x.name), ["a_short_page_resumes_below_its_last_entry", "a_first_entry_past_the_budget_is_fork_too_large_and_the_client_continues_below_its_order", "fork_too_large_on_order_zero_leaves_nothing_below"]);
|
||||
assert.ok((forkSchema.$defs.refusal.properties.code as { enum: string[] }).enum.includes("fork_too_large"));
|
||||
const covered: number[] = [];
|
||||
for (const s of sc) {
|
||||
let last: string | null | undefined = undefined;
|
||||
s.steps.forEach((step: any, i: number) => {
|
||||
if (last !== undefined) assert.equal(step.request.cursor, last, `${s.name}: the step follows the cursor or the refused order`);
|
||||
const full = judgeList(s.entries, s.ordinal, step.request);
|
||||
if (step.response !== undefined) {
|
||||
const r = step.response as any, n = r.forks.length;
|
||||
assert.ok(check(forkSchema, "list", r), s.name);
|
||||
assert.equal(hexOf(r), step.responseHex, s.name);
|
||||
assert.deepEqual(r.forks, full.forks.slice(0, n), `${s.name}: a prefix of the unbudgeted page`);
|
||||
assert.ok(n >= 1, s.name);
|
||||
assert.equal(r.next_cursor, full.next_cursor !== null || n < full.forks.length ? `c${r.forks[n - 1].card.order}` : null, s.name);
|
||||
assert.equal(r.stamp, full.stamp); assert.equal(r.open_count, full.open_count); assert.equal(r.recorded_count, full.recorded_count);
|
||||
covered.push(...r.forks.map((f: any) => f.card.order));
|
||||
last = r.next_cursor;
|
||||
} else {
|
||||
const r = step.refusal, first = full.forks[0].card as any;
|
||||
assert.ok(check(forkSchema, "refusal", r), s.name);
|
||||
assert.equal(hexOf(r), step.refusalHex, s.name);
|
||||
assert.equal(r.code, "fork_too_large");
|
||||
assert.equal(step.httpStatus, 422);
|
||||
assert.equal(step.fork_id, first.fork_id); assert.equal(step.order, first.order);
|
||||
assert.equal(r.error, `fork_too_large: fork ${first.fork_id} (order ${first.order}) alone is past the response budget; read it by id`);
|
||||
assert.ok(step.request.limit >= 1, "limit 0 never refuses");
|
||||
if (first.order >= 1) { assert.ok(i + 1 < s.steps.length, s.name); last = `c${first.order}`; assert.ok(check(forkSchema, "cursor", last)); }
|
||||
else { assert.equal(i, s.steps.length - 1, `${s.name}: order 0 is the end`); assert.equal(check(forkSchema, "cursor", "c0"), false); last = undefined; }
|
||||
}
|
||||
});
|
||||
}
|
||||
assert.deepEqual(sc[0].steps.map((x: any) => x.response.next_cursor), ["c2", null]);
|
||||
assert.deepEqual(sc[0].steps.map((x: any) => x.response.forks.length), [2, 2]);
|
||||
assert.deepEqual([sc[1].steps[0].order, sc[2].steps[1].order], [3, 0]);
|
||||
assert.deepEqual(covered, [3, 2, 1, 0, 2, 1, 0, 1]);
|
||||
});
|
||||
|
||||
test("fork transport: auth is bearer, then audience, then scope, and a votes token touches nothing", () => {
|
||||
const codes = (forkSchema.$defs.refusal.properties.code as { enum: string[] }).enum;
|
||||
const seen = new Set<string>();
|
||||
|
|
|
|||
|
|
@ -358,7 +358,7 @@ scen += [
|
|||
sc("R1_a_target_less_retraction_with_retracted_flipped_is_body_invalid_before_any_request_id_lookup", CARD, [X1, rf(IL_B, sr("n2"), "bad_retraction"), door(dict(IL_B, retracted=False), sr("n9"), "body_invalid")], "n1"),
|
||||
]
|
||||
REASON_CODES = ["stale_fork", "not_a_decider", "fork_closed", "choice_invalid", "bad_retraction", "already_retracted"]
|
||||
HTTP = {"unauthenticated": 401, "body_forbidden_field": 400, "body_unknown_field": 400, "body_invalid": 400, "fork_unknown": 404, "insufficient_scope": 403, "forbidden": 403, "request_id_conflict": 409, "request_invalid": 400,
|
||||
HTTP = {"unauthenticated": 401, "body_forbidden_field": 400, "body_unknown_field": 400, "body_invalid": 400, "fork_unknown": 404, "insufficient_scope": 403, "forbidden": 403, "request_id_conflict": 409, "request_invalid": 400, "fork_too_large": 422,
|
||||
"stale_fork": 409, "not_a_decider": 403, "fork_closed": 409, "choice_invalid": 400, "bad_retraction": 400, "already_retracted": 409}
|
||||
STORED_REFUSALS = []
|
||||
for reason in REASON_CODES:
|
||||
|
|
@ -422,6 +422,24 @@ LISTS = [
|
|||
{"name": "order_zero_is_legal_but_no_cursor_names_it", "ordinal": 9, "entries": ORDER0, "steps": [lstep(ORDER0, 9, lreq(limit=3)), lstep(ORDER0, 9, lreq(cursor="c1", limit=3))]},
|
||||
{"name": "a_mind_with_no_forks", "ordinal": 7, "entries": [], "steps": [lstep([], 7, lreq(mind="nibu", limit=10))]},
|
||||
]
|
||||
# A page may stop short of `limit` when the next entry would pass the receiver's response budget; a list whose first
|
||||
# entry alone passes it is refused `fork_too_large` (422). The budget is not in the vector: a step records what the receiver answered, and the
|
||||
# judges check it against the unbudgeted page (a nonempty strict prefix, resumed below its last entry) or against the entry the page would open with.
|
||||
def too_large_doc(c): return {"schemaVersion": "gamecult.fork.refusal.v0", "code": "fork_too_large", "error": f"fork_too_large: fork {c['fork_id']} (order {c['order']}) alone is past the response budget; read it by id"}
|
||||
def short_step(entries, ordinal, rq, keep):
|
||||
full = judge_list(entries, ordinal, rq); page = full["forks"][:keep]
|
||||
assert 0 < keep < len(full["forks"])
|
||||
r = dict(full, forks=page, next_cursor=f"c{page[-1]['card']['order']}"); return {"request": rq, "response": r, "responseHex": hx(r)}
|
||||
def large_step(entries, ordinal, rq):
|
||||
c = judge_list(entries, ordinal, rq)["forks"][0]["card"]; r = too_large_doc(c)
|
||||
return {"request": rq, "refusal": r, "refusalHex": hx(r), "httpStatus": HTTP["fork_too_large"], "fork_id": c["fork_id"], "order": c["order"]}
|
||||
SHORT_PAGES = [
|
||||
{"name": "a_short_page_resumes_below_its_last_entry", "ordinal": 9, "entries": ORDER0, "steps": [short_step(ORDER0, 9, lreq(limit=3), 2), lstep(ORDER0, 9, lreq(cursor="c2", limit=3))]},
|
||||
{"name": "a_first_entry_past_the_budget_is_fork_too_large_and_the_client_continues_below_its_order", "ordinal": 9, "entries": ORDER0,
|
||||
"steps": [large_step(ORDER0, 9, lreq(limit=3)), lstep(ORDER0, 9, lreq(cursor="c3", limit=3))]},
|
||||
{"name": "fork_too_large_on_order_zero_leaves_nothing_below", "ordinal": 9, "entries": ORDER0[2:],
|
||||
"steps": [short_step(ORDER0[2:], 9, lreq(limit=5), 1), large_step(ORDER0[2:], 9, lreq(cursor="c1", limit=5))]},
|
||||
]
|
||||
DUP_LIST = judge_list(ENTRIES + [fentry(q4b)], 412, lreq(limit=50))
|
||||
assert order_shared(DUP_LIST["forks"]) == ["order 4 is shared by gamecult-chat:question:q4 and gamecult-chat:question:q4b"]
|
||||
TOK = lambda aud, *sc, active=True: {"audience": aud, "scopes": list(sc), "active": active}
|
||||
|
|
@ -586,7 +604,7 @@ fork = {
|
|||
("no_answerer", "answerer is stamped on every stored answer", dict(CNT, answerer="")),
|
||||
]]},
|
||||
"results": [{"name": r["name"], "httpStatus": r["httpStatus"], "message": r["message"], "messagePackHex": hx(r["message"])} for r in STORED_REFUSALS],
|
||||
"doorRefusals": [{"name": c, "httpStatus": HTTP[c], "message": refusal_doc(c), "messagePackHex": hx(refusal_doc(c))} for c in TAIL],
|
||||
"doorRefusals": [{"name": c, "httpStatus": HTTP[c], "message": refusal_doc(c), "messagePackHex": hx(refusal_doc(c))} for c in TAIL] + [{"name": "fork_too_large", "httpStatus": HTTP["fork_too_large"], "message": SHORT_PAGES[1]["steps"][0]["refusal"], "messagePackHex": SHORT_PAGES[1]["steps"][0]["refusalHex"]}],
|
||||
"unavailable": [{"name": "second_door_down", "httpStatus": 503, "message": unavailable_doc(30), "messagePackHex": hx(unavailable_doc(30))},
|
||||
{"name": "second_door_down_no_hint", "httpStatus": 503, "message": unavailable_doc(None), "messagePackHex": hx(unavailable_doc(None))}],
|
||||
"rulingCites": cites,
|
||||
|
|
@ -618,6 +636,7 @@ fork = {
|
|||
+ [dict(bad("two_cards_share_one_order", "rule: order is a unique rank per Mind; the refusal names both fork ids (not expressible in the schema)", DUP_LIST),
|
||||
messagePackHex=hx(DUP_LIST), refusal=order_shared(DUP_LIST["forks"])[0])],
|
||||
"listScenarios": [{"name": l["name"], "ordinal": l["ordinal"], "entries": l["entries"], "steps": l["steps"]} for l in LISTS],
|
||||
"listShortPages": SHORT_PAGES,
|
||||
"mindRoutes": {"minds": MINDS, "forks": MIND_FORKS, "cases": MINDCASES, "sameResponse": SAME_RESPONSE},
|
||||
"authCases": [{"name": a[0], "route": a[1], "token": a[2], "expect": a[3], "huginn_down": len(a) > 4} for a in AUTH],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -692,6 +692,55 @@ namespace GameCult.Networking.Tests
|
|||
Assert.That(Errors(ForkSchema, "cursor", JsonValue.Create("c0")), Is.Not.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ForkTransport_AShortPage_AndForkTooLarge422_ContinuedBelowItsOrder()
|
||||
{
|
||||
var sc = Fork["transport"]!["listShortPages"]!.AsArray();
|
||||
Assert.That(sc.Select(x => S(x!["name"])), Is.EqualTo(new[] { "a_short_page_resumes_below_its_last_entry", "a_first_entry_past_the_budget_is_fork_too_large_and_the_client_continues_below_its_order", "fork_too_large_on_order_zero_leaves_nothing_below" }));
|
||||
var schema = JsonNode.Parse(File.ReadAllText(Path.Combine(RepoRoot(), "contracts", "cultnet", ForkSchema)))!;
|
||||
Assert.That(schema["$defs"]!["refusal"]!["properties"]!["code"]!["enum"]!.AsArray().Select(S), Does.Contain("fork_too_large"));
|
||||
var covered = new List<long>();
|
||||
foreach (var s in sc)
|
||||
{
|
||||
var entries = s!["entries"]!.AsArray(); var steps = s["steps"]!.AsArray(); string? last = null; var follow = false;
|
||||
for (var i = 0; i < steps.Count; i++)
|
||||
{
|
||||
var step = steps[i]!; var name = S(s["name"]);
|
||||
if (follow) Assert.That(step["request"]!["cursor"] is null ? null : S(step["request"]!["cursor"]), Is.EqualTo(last), name);
|
||||
var full = JudgeList(entries, s["ordinal"]!.GetValue<long>(), step["request"]!);
|
||||
var fullForks = full["forks"]!.AsArray();
|
||||
if (step["response"] is not null)
|
||||
{
|
||||
var r = step["response"]!; var forks = r["forks"]!.AsArray(); var n = forks.Count;
|
||||
Assert.That(Errors(ForkSchema, "list", r), Is.Empty, name);
|
||||
Assert.That(HexOf(r), Is.EqualTo(S(step["responseHex"])), name);
|
||||
Assert.That(n, Is.GreaterThanOrEqualTo(1), name);
|
||||
for (var k = 0; k < n; k++) Assert.That(JsonNode.DeepEquals(forks[k], fullForks[k]), Is.True, $"{name}: a prefix of the unbudgeted page");
|
||||
var o = forks[n - 1]!["card"]!["order"]!.GetValue<long>();
|
||||
if (full["next_cursor"] is not null || n < fullForks.Count) Assert.That(S(r["next_cursor"]), Is.EqualTo($"c{o}"), name); else Assert.That(r["next_cursor"], Is.Null, name);
|
||||
Assert.That(S(r["stamp"]), Is.EqualTo(S(full["stamp"]))); Assert.That(r["open_count"]!.GetValue<int>(), Is.EqualTo(full["open_count"]!.GetValue<int>())); Assert.That(r["recorded_count"]!.GetValue<int>(), Is.EqualTo(full["recorded_count"]!.GetValue<int>()));
|
||||
covered.AddRange(forks.Select(f => f!["card"]!["order"]!.GetValue<long>()));
|
||||
last = r["next_cursor"] is null ? null : S(r["next_cursor"]); follow = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var r = step["refusal"]!; var first = fullForks[0]!["card"]!;
|
||||
Assert.That(Errors(ForkSchema, "refusal", r), Is.Empty, name);
|
||||
Assert.That(HexOf(r), Is.EqualTo(S(step["refusalHex"])), name);
|
||||
Assert.That(S(r["code"]), Is.EqualTo("fork_too_large")); Assert.That(step["httpStatus"]!.GetValue<int>(), Is.EqualTo(422));
|
||||
Assert.That(S(step["fork_id"]), Is.EqualTo(S(first["fork_id"]))); Assert.That(step["order"]!.GetValue<long>(), Is.EqualTo(first["order"]!.GetValue<long>()));
|
||||
Assert.That(S(r["error"]), Is.EqualTo($"fork_too_large: fork {S(first["fork_id"])} (order {first["order"]!.GetValue<long>()}) alone is past the response budget; read it by id"));
|
||||
Assert.That(step["request"]!["limit"]!.GetValue<int>(), Is.GreaterThanOrEqualTo(1));
|
||||
var order = first["order"]!.GetValue<long>();
|
||||
if (order >= 1) { Assert.That(i + 1, Is.LessThan(steps.Count), name); last = $"c{order}"; follow = true; Assert.That(Errors(ForkSchema, "cursor", JsonValue.Create(last)), Is.Empty); }
|
||||
else { Assert.That(i, Is.EqualTo(steps.Count - 1), $"{name}: order 0 is the end"); follow = false; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Assert.That(covered, Is.EqualTo(new long[] { 3, 2, 1, 0, 2, 1, 0, 1 }));
|
||||
Assert.That(sc[0]!["steps"]!.AsArray().Select(x => x!["response"]!["forks"]!.AsArray().Count), Is.EqualTo(new[] { 2, 2 }));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ForkTransport_Auth_BearerThenAudienceThenScope_AVotesTokenTouchesNothing()
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in a new issue