feat(app): define trusted rewind branch flow

This commit is contained in:
Codex
2026-07-28 03:23:57 -04:00
parent cf9507a9dd
commit 497c127e87
15 changed files with 671 additions and 26 deletions
+1 -1
View File
@@ -1 +1 @@
25441c099be603f4bcbe46079868ef851fded230d8bcda6dd627a027d80ba78c
93d800013b9bec3087490ae629039724076cf26d9bb9a5f8834c6aa7473b1ca7
@@ -0,0 +1,30 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "ForkBranchRequest",
"description": "Requests a new playable branch rooted at an already committed story node.\n\nThe caller identifies the branch head it observed so the trusted runtime can\nreject a rewind racing with a newer turn. The new branch id is generated by\nthe runtime from these trusted inputs and is never supplied by the UI or the\nmodel.",
"type": "object",
"properties": {
"actionId": {
"type": "string"
},
"currentBranchId": {
"type": "string"
},
"expectedCurrentNodeId": {
"type": "string"
},
"sourceNodeId": {
"type": "string"
},
"storyId": {
"type": "string"
}
},
"required": [
"storyId",
"currentBranchId",
"expectedCurrentNodeId",
"sourceNodeId",
"actionId"
]
}
@@ -0,0 +1,397 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "ForkBranchResult",
"type": "object",
"properties": {
"branchId": {
"type": "string"
},
"playerView": {
"$ref": "#/$defs/PlayerView"
}
},
"required": [
"branchId",
"playerView"
],
"$defs": {
"ActionSuggestion": {
"type": "object",
"properties": {
"draft": {
"type": "string"
},
"id": {
"type": "string"
},
"label": {
"type": "string"
}
},
"required": [
"id",
"label",
"draft"
]
},
"BeatKind": {
"type": "string",
"enum": [
"narration",
"dialogue",
"action",
"system"
]
},
"HistoryNodeView": {
"type": "object",
"properties": {
"branchId": {
"type": "string"
},
"id": {
"type": "string"
},
"isCurrent": {
"type": "boolean"
},
"label": {
"type": "string"
},
"parentId": {
"type": [
"string",
"null"
]
}
},
"required": [
"id",
"branchId",
"label",
"isCurrent"
]
},
"ItemPlacement": {
"type": "string",
"enum": [
"bag",
"worn",
"hand",
"scene",
"hidden"
]
},
"KnowledgeCertainty": {
"type": "string",
"enum": [
"suspected",
"reported",
"confirmed"
]
},
"PlayerItemView": {
"type": "object",
"properties": {
"condition": {
"type": "string"
},
"description": {
"type": "string"
},
"instanceId": {
"type": "string"
},
"name": {
"type": "string"
},
"placement": {
"$ref": "#/$defs/ItemPlacement"
},
"quantity": {
"type": "integer",
"format": "uint32",
"minimum": 0
}
},
"required": [
"instanceId",
"name",
"description",
"quantity",
"placement",
"condition"
]
},
"PlayerKnowledgeView": {
"type": "object",
"properties": {
"certainty": {
"$ref": "#/$defs/KnowledgeCertainty"
},
"id": {
"type": "string"
},
"summary": {
"type": "string"
},
"title": {
"type": "string"
}
},
"required": [
"id",
"title",
"summary",
"certainty"
]
},
"PlayerPromiseView": {
"type": "object",
"properties": {
"content": {
"type": "string"
},
"id": {
"type": "string"
},
"status": {
"$ref": "#/$defs/PromiseStatus"
},
"weight": {
"$ref": "#/$defs/PromiseWeight"
}
},
"required": [
"id",
"content",
"status",
"weight"
]
},
"PlayerView": {
"type": "object",
"properties": {
"beats": {
"type": "array",
"items": {
"$ref": "#/$defs/PresentationBeat"
}
},
"branchId": {
"type": "string"
},
"canContinue": {
"type": "boolean"
},
"characterExpression": {
"type": [
"string",
"null"
],
"default": null
},
"characterName": {
"type": "string"
},
"characterPose": {
"type": [
"string",
"null"
],
"default": null
},
"history": {
"type": "array",
"default": [],
"items": {
"$ref": "#/$defs/HistoryNodeView"
}
},
"inventory": {
"type": "array",
"default": [],
"items": {
"$ref": "#/$defs/PlayerItemView"
}
},
"knowledge": {
"type": "array",
"default": [],
"items": {
"$ref": "#/$defs/PlayerKnowledgeView"
}
},
"nodeId": {
"type": "string"
},
"promises": {
"type": "array",
"default": [],
"items": {
"$ref": "#/$defs/PlayerPromiseView"
}
},
"relationship": {
"$ref": "#/$defs/RelationshipView"
},
"sceneId": {
"type": "string"
},
"sceneTitle": {
"type": "string"
},
"storyId": {
"type": "string"
},
"suggestions": {
"type": "array",
"default": [],
"items": {
"$ref": "#/$defs/ActionSuggestion"
}
}
},
"required": [
"storyId",
"nodeId",
"branchId",
"sceneId",
"sceneTitle",
"characterName",
"beats",
"relationship",
"canContinue"
]
},
"PresentationBeat": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"kind": {
"$ref": "#/$defs/BeatKind"
},
"speaker": {
"type": [
"string",
"null"
]
},
"text": {
"type": "string"
},
"visual": {
"anyOf": [
{
"$ref": "#/$defs/VisualDirective"
},
{
"type": "null"
}
]
}
},
"required": [
"id",
"kind",
"text"
]
},
"PromiseStatus": {
"type": "string",
"enum": [
"proposed",
"accepted",
"fulfilled",
"broken",
"released",
"impossible"
]
},
"PromiseWeight": {
"type": "string",
"enum": [
"minor",
"major"
]
},
"RelationshipBand": {
"type": "string",
"enum": [
"distant",
"guarded",
"warming",
"close",
"bonded"
]
},
"RelationshipView": {
"type": "object",
"properties": {
"affinity": {
"$ref": "#/$defs/RelationshipBand"
},
"attachment": {
"$ref": "#/$defs/RelationshipBand"
},
"hope": {
"$ref": "#/$defs/RelationshipBand"
},
"intimacy": {
"$ref": "#/$defs/RelationshipBand"
},
"respect": {
"$ref": "#/$defs/RelationshipBand"
},
"trust": {
"$ref": "#/$defs/RelationshipBand"
},
"updatedAtNode": {
"type": [
"string",
"null"
]
}
},
"required": [
"affinity",
"trust",
"hope",
"respect",
"intimacy",
"attachment"
]
},
"VisualDirective": {
"type": "object",
"properties": {
"character": {
"type": [
"string",
"null"
]
},
"expression": {
"type": [
"string",
"null"
]
},
"pose": {
"type": [
"string",
"null"
]
},
"scene": {
"type": [
"string",
"null"
]
}
}
}
}
}
+4
View File
@@ -116,6 +116,10 @@ export type TurnRequest = { storyId: string, branchId: string, expectedNodeId: s
export type TurnResult = { committedNodeId: string, playerView: PlayerView, };
export type ForkBranchRequest = { storyId: string, currentBranchId: string, expectedCurrentNodeId: string, sourceNodeId: string, actionId: string, };
export type ForkBranchResult = { branchId: string, playerView: PlayerView, };
export type TurnFailureCode = "stale_node" | "invalid_input" | "invalid_model_output" | "provider_unavailable" | "cancelled" | "timed_out" | "internal";
export type TurnFailure = { code: TurnFailureCode, message: string, retryable: boolean, };