session types: boundary actions and captures in the step trace, a capture never precedes a slot save

Review round 1, B1 and N4. TraceBehaviour.boundaryActions records the slot saves and the rollback
at the reached boundary in application order; TraceOperational.captures records each capture with
the number of boundary actions before it; TransitionTrace refuses a capture taken before the
boundary's slot saves. Both languages, fixtures for the rule, synthetic coordinator records both
lists empty. contractDigest moves to a56e25e6.
This commit is contained in:
acamilo 2026-09-23 09:00:11 +00:00
parent 24ba933500
commit a6e1623698
9 changed files with 1344 additions and 49 deletions

View file

@ -23,6 +23,25 @@ import {
ownerToken,
} from './scalar';
import { MAX_AGENTS, MAX_RATE_ROLES } from './workers';
import { MAX_SLOTS } from './extensions';
/** Boundary actions at the reached boundary (amendment of 2026-09-23, RT-01a). */
export const BOUNDARY_ACTION_KINDS = ['save-slot', 'rollback'] as const;
export type BoundaryActionKind = (typeof BOUNDARY_ACTION_KINDS)[number];
/** Every slot at most once, plus one rollback. */
export const MAX_BOUNDARY_ACTIONS = MAX_SLOTS + 1;
export interface BoundaryAction {
kind: BoundaryActionKind;
slotId: Id;
stateDigest: Digest | null;
}
/** A checkpoint capture at the reached boundary and how many boundary actions preceded it. */
export interface TraceCapture {
checkpointId: Id;
afterActions: number;
}
export interface TraceAgent {
agentId: Id;
@ -49,6 +68,7 @@ export interface TraceBehaviour {
outcomeIds: Id[];
eventIds: Id[];
publishedBoundary: U64;
boundaryActions: BoundaryAction[];
}
export interface TraceRequest {
@ -63,6 +83,7 @@ export interface TraceOperational {
commitRequestIds: TraceRequest[];
busCallIds: BusCallId[];
deliveryIds: OwnerToken[];
captures: TraceCapture[];
}
export interface TransitionTrace {
@ -107,7 +128,18 @@ export function readTraceBehaviour(value: unknown): TraceBehaviour {
const outcomeIds = reader.idList('outcomeIds', 0, MAX_RATE_ROLES);
const eventIds = reader.idList('eventIds', 0, MAX_RATE_ROLES);
const publishedBoundary = reader.u64('publishedBoundary');
const boundaryActions = reader.list('boundaryActions', 0, MAX_BOUNDARY_ACTIONS, (item) => {
const action = new Reader(item, 'TraceBehaviour.boundaryActions');
const entry: BoundaryAction = {
kind: action.enumeration('kind', BOUNDARY_ACTION_KINDS),
slotId: action.id('slotId'),
stateDigest: action.value('stateDigest') === null ? null : action.digest('stateDigest'),
};
action.finish();
return entry;
});
reader.finish();
validateBoundaryActions(boundaryActions);
requireUnique(
agents.map((agent) => agent.agentId),
@ -141,9 +173,39 @@ export function readTraceBehaviour(value: unknown): TraceBehaviour {
outcomeIds,
eventIds,
publishedBoundary,
boundaryActions,
};
}
/** Slot saves first, each slot once with its digest; at most one rollback, last, no digest. */
function validateBoundaryActions(actions: readonly BoundaryAction[]): void {
let rolledBack = false;
const saved: string[] = [];
for (const action of actions) {
if (rolledBack) fail('TraceBehaviour: nothing follows a rollback at the same boundary');
if (action.kind === 'save-slot') {
if (action.stateDigest === null) fail('TraceBehaviour: a slot save records its state digest');
if (saved.includes(action.slotId)) {
fail('TraceBehaviour: a slot is saved at most once per boundary');
}
saved.push(action.slotId);
} else {
if (action.stateDigest !== null) fail('TraceBehaviour: a rollback records no state digest');
rolledBack = true;
}
}
}
/** How many leading boundary actions are slot saves. */
export function slotSaves(behaviour: TraceBehaviour): number {
let count = 0;
for (const action of behaviour.boundaryActions) {
if (action.kind !== 'save-slot') break;
count += 1;
}
return count;
}
export function readTraceOperational(value: unknown): TraceOperational {
const reader = new Reader(value, 'TraceOperational');
const readRequests = (item: unknown): TraceRequest => {
@ -162,6 +224,15 @@ export function readTraceOperational(value: unknown): TraceOperational {
commitRequestIds: reader.list('commitRequestIds', 1, MAX_AGENTS, readRequests),
busCallIds: reader.list('busCallIds', 0, 64, busCallId),
deliveryIds: reader.list('deliveryIds', 0, 64, ownerToken),
captures: reader.list('captures', 0, MAX_BOUNDARY_ACTIONS + 1, (item) => {
const capture = new Reader(item, 'TraceOperational.captures');
const entry: TraceCapture = {
checkpointId: capture.id('checkpointId'),
afterActions: capture.int('afterActions', 0, MAX_BOUNDARY_ACTIONS),
};
capture.finish();
return entry;
}),
};
reader.finish();
requireUnique(
@ -174,6 +245,17 @@ export function readTraceOperational(value: unknown): TraceOperational {
);
requireUnique(operational.busCallIds, 'TraceOperational.busCallIds');
requireUnique(operational.deliveryIds, 'TraceOperational.deliveryIds');
requireUnique(
operational.captures.map((capture) => capture.checkpointId),
'TraceOperational.captures',
);
let last = 0;
for (const capture of operational.captures) {
if (capture.afterActions < last) {
fail('TraceOperational: captures are recorded in the order they were taken');
}
last = capture.afterActions;
}
return operational;
}
@ -194,6 +276,20 @@ export function readTransitionTrace(value: unknown): TransitionTrace {
}
}
}
// A slot save due at a boundary completes before any capture there (legacy-gameboy-v1 16).
const saves = slotSaves(trace.behaviour);
for (const capture of trace.operational.captures) {
if (capture.afterActions < saves) {
fail(
`TransitionTrace: capture "${capture.checkpointId}" was taken before this boundary's slot saves completed`,
);
}
if (capture.afterActions > trace.behaviour.boundaryActions.length) {
fail(
`TransitionTrace: capture "${capture.checkpointId}" counts more boundary actions than were applied`,
);
}
}
return trace;
}
@ -242,6 +338,7 @@ export function behaviourDiff(left: TransitionTrace, right: TransitionTrace): st
}
if (differs(a.outcomeIds, b.outcomeIds)) out.push('outcomeIds differ');
if (differs(a.eventIds, b.eventIds)) out.push('eventIds differ');
if (differs(a.boundaryActions, b.boundaryActions)) out.push('boundaryActions differ');
const idsA = a.agents.map((agent) => agent.agentId);
const idsB = b.agents.map((agent) => agent.agentId);
if (differs(idsA, idsB)) {

View file

@ -1,9 +1,9 @@
{
"description": "contractDigest is the SHA-256 of the canonical schema set in schema-set.json.",
"contractDigest": "f61e8f4fa9336184ebb6f0305f9d6135872707ef5536d51b9933df8943653c77",
"contractDigest": "a56e25e6289a6f9e6720ba5f727651e9e4e2c5cf7a68990fc1f8d37fe12f2006",
"schemaSetVersion": 1,
"schemaSetBytes": 30184,
"schemaSetBytes": 30711,
"types": 60,
"enums": 11,
"enums": 12,
"limits": 27
}

View file

@ -4387,7 +4387,8 @@
"observationBoundaries": [],
"outcomeIds": [],
"eventIds": [],
"publishedBoundary": "42"
"publishedBoundary": "42",
"boundaryActions": []
},
"operational": {
"wallTimeNs": "1",
@ -4405,7 +4406,8 @@
}
],
"busCallIds": [],
"deliveryIds": []
"deliveryIds": [],
"captures": []
}
},
"reason": "a commit acknowledges the transition's next boundary"
@ -5759,6 +5761,719 @@
"flysimCompatibility": "lif-1ms-f64-v2/pokered-unique8-v6/75ba5d3536a2862fdb9f4ef1a76b96fe099a7201ac737f0cf53fe4c5ad4183f3:1657ba7716494c9db95a13b1527bc129226363f99b395774de1f76ff28571f0e:63b1acb26272edccdcfacf3c2ff58069e0cefaa84451429c989258bafaeae1d5:f567d7f07227e71c0df2ab4e6510f3a3f79e51b675095792e16d216d74b7b5b7:ece0b5e76d1884dd2f3ff6e0362bc284febe205ac1ce07fdab5009942ddffb62:b8c33144d4cec31c3ac4b6091ef1f4207f567c4f710f7c13fd2dc47c44c2b634:dbbafc044cd50aad7b792615988ff6d9991c846cc3d8b2eafc86b7c357b5eefc/fly-kc-mbon-rstdp-v2/binjgb:c60e138da5a795ebb55e56b11b7e90024e41112c/pokered:0cd19d3b877b7dc66d12c7050bed9a7f38154d4b/statefmt:199616-x86_64-unknown-linux-gnu"
},
"reason": "FLYSIM01 stays until RETIRE-01"
},
{
"name": "a capture taken before the boundary's slot save",
"type": "TransitionTrace",
"value": {
"behaviour": {
"scope": {
"sessionId": "demo",
"epoch": "epoch-1",
"step": "41"
},
"agents": [
{
"agentId": "fly-a",
"profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0",
"ticksAdvanced": "17",
"brainTicks": "2517",
"remainder": {
"numerator": "1000000",
"denominator": "3"
},
"decisionDigest": "a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b",
"committedStep": "42"
},
{
"agentId": "fly-b",
"profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0",
"ticksAdvanced": "17",
"brainTicks": "2550",
"remainder": {
"numerator": "0",
"denominator": "1"
},
"decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e",
"committedStep": "42"
}
],
"batchId": "batch-41",
"controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6",
"acknowledgedBoundary": "42",
"observationBoundaries": [
{
"viewId": "screen",
"producedStep": "42"
}
],
"outcomeIds": [
"outcome-1"
],
"eventIds": [
"evt-1"
],
"publishedBoundary": "42",
"boundaryActions": [
{
"kind": "save-slot",
"slotId": "best",
"stateDigest": "5d41402abc4b2a76b9719d911017c592ae9d3a0c8a7a6f5d2e3d4c5b6a7f8e9d"
}
]
},
"operational": {
"wallTimeNs": "1234567890",
"prepareRequestIds": [
{
"agentId": "fly-a",
"requestId": "req-41"
},
{
"agentId": "fly-b",
"requestId": "req-42"
}
],
"advanceRequestId": "req-43",
"commitRequestIds": [
{
"agentId": "fly-a",
"requestId": "req-44"
},
{
"agentId": "fly-b",
"requestId": "req-45"
}
],
"busCallIds": [
"call-100",
"call-101",
"call-102"
],
"deliveryIds": [
"dlv-7",
"own-9"
],
"captures": [
{
"checkpointId": "milestone-11",
"afterActions": 0
}
]
}
},
"reason": "a checkpoint must not pair the new ledger with the old slot (review round 1, B1)"
},
{
"name": "a capture counting actions that were never applied",
"type": "TransitionTrace",
"value": {
"behaviour": {
"scope": {
"sessionId": "demo",
"epoch": "epoch-1",
"step": "41"
},
"agents": [
{
"agentId": "fly-a",
"profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0",
"ticksAdvanced": "17",
"brainTicks": "2517",
"remainder": {
"numerator": "1000000",
"denominator": "3"
},
"decisionDigest": "a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b",
"committedStep": "42"
},
{
"agentId": "fly-b",
"profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0",
"ticksAdvanced": "17",
"brainTicks": "2550",
"remainder": {
"numerator": "0",
"denominator": "1"
},
"decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e",
"committedStep": "42"
}
],
"batchId": "batch-41",
"controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6",
"acknowledgedBoundary": "42",
"observationBoundaries": [
{
"viewId": "screen",
"producedStep": "42"
}
],
"outcomeIds": [
"outcome-1"
],
"eventIds": [
"evt-1"
],
"publishedBoundary": "42",
"boundaryActions": [
{
"kind": "save-slot",
"slotId": "best",
"stateDigest": "5d41402abc4b2a76b9719d911017c592ae9d3a0c8a7a6f5d2e3d4c5b6a7f8e9d"
}
]
},
"operational": {
"wallTimeNs": "1234567890",
"prepareRequestIds": [
{
"agentId": "fly-a",
"requestId": "req-41"
},
{
"agentId": "fly-b",
"requestId": "req-42"
}
],
"advanceRequestId": "req-43",
"commitRequestIds": [
{
"agentId": "fly-a",
"requestId": "req-44"
},
{
"agentId": "fly-b",
"requestId": "req-45"
}
],
"busCallIds": [
"call-100",
"call-101",
"call-102"
],
"deliveryIds": [
"dlv-7",
"own-9"
],
"captures": [
{
"checkpointId": "milestone-11",
"afterActions": 2
}
]
}
},
"reason": "afterActions <= the boundary's actions"
},
{
"name": "a slot save after a rollback",
"type": "TransitionTrace",
"value": {
"behaviour": {
"scope": {
"sessionId": "demo",
"epoch": "epoch-1",
"step": "41"
},
"agents": [
{
"agentId": "fly-a",
"profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0",
"ticksAdvanced": "17",
"brainTicks": "2517",
"remainder": {
"numerator": "1000000",
"denominator": "3"
},
"decisionDigest": "a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b",
"committedStep": "42"
},
{
"agentId": "fly-b",
"profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0",
"ticksAdvanced": "17",
"brainTicks": "2550",
"remainder": {
"numerator": "0",
"denominator": "1"
},
"decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e",
"committedStep": "42"
}
],
"batchId": "batch-41",
"controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6",
"acknowledgedBoundary": "42",
"observationBoundaries": [
{
"viewId": "screen",
"producedStep": "42"
}
],
"outcomeIds": [
"outcome-1"
],
"eventIds": [
"evt-1"
],
"publishedBoundary": "42",
"boundaryActions": [
{
"kind": "rollback",
"slotId": "best",
"stateDigest": null
},
{
"kind": "save-slot",
"slotId": "best",
"stateDigest": "5d41402abc4b2a76b9719d911017c592ae9d3a0c8a7a6f5d2e3d4c5b6a7f8e9d"
}
]
},
"operational": {
"wallTimeNs": "1234567890",
"prepareRequestIds": [
{
"agentId": "fly-a",
"requestId": "req-41"
},
{
"agentId": "fly-b",
"requestId": "req-42"
}
],
"advanceRequestId": "req-43",
"commitRequestIds": [
{
"agentId": "fly-a",
"requestId": "req-44"
},
{
"agentId": "fly-b",
"requestId": "req-45"
}
],
"busCallIds": [
"call-100",
"call-101",
"call-102"
],
"deliveryIds": [
"dlv-7",
"own-9"
],
"captures": []
}
},
"reason": "nothing follows a rollback at the same boundary"
},
{
"name": "a slot save without its state digest",
"type": "TransitionTrace",
"value": {
"behaviour": {
"scope": {
"sessionId": "demo",
"epoch": "epoch-1",
"step": "41"
},
"agents": [
{
"agentId": "fly-a",
"profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0",
"ticksAdvanced": "17",
"brainTicks": "2517",
"remainder": {
"numerator": "1000000",
"denominator": "3"
},
"decisionDigest": "a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b",
"committedStep": "42"
},
{
"agentId": "fly-b",
"profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0",
"ticksAdvanced": "17",
"brainTicks": "2550",
"remainder": {
"numerator": "0",
"denominator": "1"
},
"decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e",
"committedStep": "42"
}
],
"batchId": "batch-41",
"controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6",
"acknowledgedBoundary": "42",
"observationBoundaries": [
{
"viewId": "screen",
"producedStep": "42"
}
],
"outcomeIds": [
"outcome-1"
],
"eventIds": [
"evt-1"
],
"publishedBoundary": "42",
"boundaryActions": [
{
"kind": "save-slot",
"slotId": "best",
"stateDigest": null
}
]
},
"operational": {
"wallTimeNs": "1234567890",
"prepareRequestIds": [
{
"agentId": "fly-a",
"requestId": "req-41"
},
{
"agentId": "fly-b",
"requestId": "req-42"
}
],
"advanceRequestId": "req-43",
"commitRequestIds": [
{
"agentId": "fly-a",
"requestId": "req-44"
},
{
"agentId": "fly-b",
"requestId": "req-45"
}
],
"busCallIds": [
"call-100",
"call-101",
"call-102"
],
"deliveryIds": [
"dlv-7",
"own-9"
],
"captures": []
}
},
"reason": "a slot save records the saved state's digest"
},
{
"name": "the same slot saved twice at one boundary",
"type": "TransitionTrace",
"value": {
"behaviour": {
"scope": {
"sessionId": "demo",
"epoch": "epoch-1",
"step": "41"
},
"agents": [
{
"agentId": "fly-a",
"profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0",
"ticksAdvanced": "17",
"brainTicks": "2517",
"remainder": {
"numerator": "1000000",
"denominator": "3"
},
"decisionDigest": "a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b",
"committedStep": "42"
},
{
"agentId": "fly-b",
"profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0",
"ticksAdvanced": "17",
"brainTicks": "2550",
"remainder": {
"numerator": "0",
"denominator": "1"
},
"decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e",
"committedStep": "42"
}
],
"batchId": "batch-41",
"controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6",
"acknowledgedBoundary": "42",
"observationBoundaries": [
{
"viewId": "screen",
"producedStep": "42"
}
],
"outcomeIds": [
"outcome-1"
],
"eventIds": [
"evt-1"
],
"publishedBoundary": "42",
"boundaryActions": [
{
"kind": "save-slot",
"slotId": "best",
"stateDigest": "5d41402abc4b2a76b9719d911017c592ae9d3a0c8a7a6f5d2e3d4c5b6a7f8e9d"
},
{
"kind": "save-slot",
"slotId": "best",
"stateDigest": "5d41402abc4b2a76b9719d911017c592ae9d3a0c8a7a6f5d2e3d4c5b6a7f8e9d"
}
]
},
"operational": {
"wallTimeNs": "1234567890",
"prepareRequestIds": [
{
"agentId": "fly-a",
"requestId": "req-41"
},
{
"agentId": "fly-b",
"requestId": "req-42"
}
],
"advanceRequestId": "req-43",
"commitRequestIds": [
{
"agentId": "fly-a",
"requestId": "req-44"
},
{
"agentId": "fly-b",
"requestId": "req-45"
}
],
"busCallIds": [
"call-100",
"call-101",
"call-102"
],
"deliveryIds": [
"dlv-7",
"own-9"
],
"captures": []
}
},
"reason": "a slot is saved at most once per boundary"
},
{
"name": "an unknown boundary action",
"type": "TransitionTrace",
"value": {
"behaviour": {
"scope": {
"sessionId": "demo",
"epoch": "epoch-1",
"step": "41"
},
"agents": [
{
"agentId": "fly-a",
"profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0",
"ticksAdvanced": "17",
"brainTicks": "2517",
"remainder": {
"numerator": "1000000",
"denominator": "3"
},
"decisionDigest": "a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b",
"committedStep": "42"
},
{
"agentId": "fly-b",
"profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0",
"ticksAdvanced": "17",
"brainTicks": "2550",
"remainder": {
"numerator": "0",
"denominator": "1"
},
"decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e",
"committedStep": "42"
}
],
"batchId": "batch-41",
"controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6",
"acknowledgedBoundary": "42",
"observationBoundaries": [
{
"viewId": "screen",
"producedStep": "42"
}
],
"outcomeIds": [
"outcome-1"
],
"eventIds": [
"evt-1"
],
"publishedBoundary": "42",
"boundaryActions": [
{
"kind": "reset",
"slotId": "best",
"stateDigest": null
}
]
},
"operational": {
"wallTimeNs": "1234567890",
"prepareRequestIds": [
{
"agentId": "fly-a",
"requestId": "req-41"
},
{
"agentId": "fly-b",
"requestId": "req-42"
}
],
"advanceRequestId": "req-43",
"commitRequestIds": [
{
"agentId": "fly-a",
"requestId": "req-44"
},
{
"agentId": "fly-b",
"requestId": "req-45"
}
],
"busCallIds": [
"call-100",
"call-101",
"call-102"
],
"deliveryIds": [
"dlv-7",
"own-9"
],
"captures": []
}
},
"reason": "BoundaryActionKind is closed"
},
{
"name": "captures recorded out of order",
"type": "TransitionTrace",
"value": {
"behaviour": {
"scope": {
"sessionId": "demo",
"epoch": "epoch-1",
"step": "41"
},
"agents": [
{
"agentId": "fly-a",
"profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0",
"ticksAdvanced": "17",
"brainTicks": "2517",
"remainder": {
"numerator": "1000000",
"denominator": "3"
},
"decisionDigest": "a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b",
"committedStep": "42"
},
{
"agentId": "fly-b",
"profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0",
"ticksAdvanced": "17",
"brainTicks": "2550",
"remainder": {
"numerator": "0",
"denominator": "1"
},
"decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e",
"committedStep": "42"
}
],
"batchId": "batch-41",
"controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6",
"acknowledgedBoundary": "42",
"observationBoundaries": [
{
"viewId": "screen",
"producedStep": "42"
}
],
"outcomeIds": [
"outcome-1"
],
"eventIds": [
"evt-1"
],
"publishedBoundary": "42",
"boundaryActions": [
{
"kind": "save-slot",
"slotId": "best",
"stateDigest": "5d41402abc4b2a76b9719d911017c592ae9d3a0c8a7a6f5d2e3d4c5b6a7f8e9d"
},
{
"kind": "rollback",
"slotId": "best",
"stateDigest": null
}
]
},
"operational": {
"wallTimeNs": "1234567890",
"prepareRequestIds": [
{
"agentId": "fly-a",
"requestId": "req-41"
},
{
"agentId": "fly-b",
"requestId": "req-42"
}
],
"advanceRequestId": "req-43",
"commitRequestIds": [
{
"agentId": "fly-a",
"requestId": "req-44"
},
{
"agentId": "fly-b",
"requestId": "req-45"
}
],
"busCallIds": [
"call-100",
"call-101",
"call-102"
],
"deliveryIds": [
"dlv-7",
"own-9"
],
"captures": [
{
"checkpointId": "a",
"afterActions": 2
},
{
"checkpointId": "b",
"afterActions": 1
}
]
}
},
"reason": "captures are recorded in the order taken"
}
]
}

File diff suppressed because one or more lines are too long

View file

@ -54,7 +54,8 @@
"evt-1",
"evt-2"
],
"publishedBoundary": "42"
"publishedBoundary": "42",
"boundaryActions": []
},
"operational": {
"wallTimeNs": "1234567890",
@ -87,7 +88,8 @@
"deliveryIds": [
"dlv-7",
"own-9"
]
],
"captures": []
}
},
"variants": [
@ -147,7 +149,8 @@
"evt-1",
"evt-2"
],
"publishedBoundary": "42"
"publishedBoundary": "42",
"boundaryActions": []
},
"operational": {
"wallTimeNs": "1234567890",
@ -180,7 +183,8 @@
"deliveryIds": [
"dlv-7",
"own-9"
]
],
"captures": []
}
},
"behaviourEquals": true,
@ -242,7 +246,8 @@
"evt-1",
"evt-2"
],
"publishedBoundary": "42"
"publishedBoundary": "42",
"boundaryActions": []
},
"operational": {
"wallTimeNs": "9999999999",
@ -276,7 +281,8 @@
"deliveryIds": [
"dlv-91",
"own-92"
]
],
"captures": []
}
},
"behaviourEquals": true,
@ -338,7 +344,8 @@
"evt-1",
"evt-2"
],
"publishedBoundary": "42"
"publishedBoundary": "42",
"boundaryActions": []
},
"operational": {
"wallTimeNs": "1234567890",
@ -371,7 +378,8 @@
"deliveryIds": [
"dlv-7",
"own-9"
]
],
"captures": []
}
},
"behaviourEquals": true,
@ -433,7 +441,8 @@
"evt-1",
"evt-2"
],
"publishedBoundary": "42"
"publishedBoundary": "42",
"boundaryActions": []
},
"operational": {
"wallTimeNs": "1234567890",
@ -466,7 +475,8 @@
"deliveryIds": [
"dlv-7",
"own-9"
]
],
"captures": []
}
},
"behaviourEquals": false,
@ -528,7 +538,8 @@
"evt-1",
"evt-2"
],
"publishedBoundary": "42"
"publishedBoundary": "42",
"boundaryActions": []
},
"operational": {
"wallTimeNs": "1234567890",
@ -561,7 +572,8 @@
"deliveryIds": [
"dlv-7",
"own-9"
]
],
"captures": []
}
},
"behaviourEquals": false,
@ -623,7 +635,8 @@
"evt-1",
"evt-2"
],
"publishedBoundary": "42"
"publishedBoundary": "42",
"boundaryActions": []
},
"operational": {
"wallTimeNs": "1234567890",
@ -656,7 +669,8 @@
"deliveryIds": [
"dlv-7",
"own-9"
]
],
"captures": []
}
},
"behaviourEquals": false,
@ -718,7 +732,8 @@
"evt-1",
"evt-2"
],
"publishedBoundary": "42"
"publishedBoundary": "42",
"boundaryActions": []
},
"operational": {
"wallTimeNs": "1234567890",
@ -751,7 +766,8 @@
"deliveryIds": [
"dlv-7",
"own-9"
]
],
"captures": []
}
},
"behaviourEquals": false,
@ -813,7 +829,8 @@
"evt-1",
"evt-2"
],
"publishedBoundary": "42"
"publishedBoundary": "42",
"boundaryActions": []
},
"operational": {
"wallTimeNs": "1234567890",
@ -846,7 +863,8 @@
"deliveryIds": [
"dlv-7",
"own-9"
]
],
"captures": []
}
},
"behaviourEquals": false,
@ -908,7 +926,8 @@
"evt-1",
"evt-2"
],
"publishedBoundary": "42"
"publishedBoundary": "42",
"boundaryActions": []
},
"operational": {
"wallTimeNs": "1234567890",
@ -941,7 +960,8 @@
"deliveryIds": [
"dlv-7",
"own-9"
]
],
"captures": []
}
},
"behaviourEquals": false,
@ -1003,7 +1023,8 @@
"evt-2",
"evt-1"
],
"publishedBoundary": "42"
"publishedBoundary": "42",
"boundaryActions": []
},
"operational": {
"wallTimeNs": "1234567890",
@ -1036,7 +1057,8 @@
"deliveryIds": [
"dlv-7",
"own-9"
]
],
"captures": []
}
},
"behaviourEquals": false,
@ -1098,7 +1120,8 @@
"evt-1",
"evt-2"
],
"publishedBoundary": "42"
"publishedBoundary": "42",
"boundaryActions": []
},
"operational": {
"wallTimeNs": "1234567890",
@ -1131,7 +1154,8 @@
"deliveryIds": [
"dlv-7",
"own-9"
]
],
"captures": []
}
},
"behaviourEquals": false,

View file

@ -2763,7 +2763,8 @@
"eventIds": [
"evt-1"
],
"publishedBoundary": "42"
"publishedBoundary": "42",
"boundaryActions": []
},
"operational": {
"wallTimeNs": "1234567890",
@ -2796,12 +2797,13 @@
"deliveryIds": [
"dlv-7",
"own-9"
]
],
"captures": []
}
},
"note": "",
"canonical": "{\"behaviour\":{\"acknowledgedBoundary\":\"42\",\"agents\":[{\"agentId\":\"fly-a\",\"brainTicks\":\"2517\",\"committedStep\":\"42\",\"decisionDigest\":\"a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b\",\"profileDigest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\",\"remainder\":{\"denominator\":\"3\",\"numerator\":\"1000000\"},\"ticksAdvanced\":\"17\"},{\"agentId\":\"fly-b\",\"brainTicks\":\"2550\",\"committedStep\":\"42\",\"decisionDigest\":\"ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e\",\"profileDigest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\",\"remainder\":{\"denominator\":\"1\",\"numerator\":\"0\"},\"ticksAdvanced\":\"17\"}],\"batchId\":\"batch-41\",\"controlDigest\":\"1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6\",\"eventIds\":[\"evt-1\"],\"observationBoundaries\":[{\"producedStep\":\"42\",\"viewId\":\"screen\"}],\"outcomeIds\":[\"outcome-1\"],\"publishedBoundary\":\"42\",\"scope\":{\"epoch\":\"epoch-1\",\"sessionId\":\"demo\",\"step\":\"41\"}},\"operational\":{\"advanceRequestId\":\"req-43\",\"busCallIds\":[\"call-100\",\"call-101\",\"call-102\"],\"commitRequestIds\":[{\"agentId\":\"fly-a\",\"requestId\":\"req-44\"},{\"agentId\":\"fly-b\",\"requestId\":\"req-45\"}],\"deliveryIds\":[\"dlv-7\",\"own-9\"],\"prepareRequestIds\":[{\"agentId\":\"fly-a\",\"requestId\":\"req-41\"},{\"agentId\":\"fly-b\",\"requestId\":\"req-42\"}],\"wallTimeNs\":\"1234567890\"}}",
"digest": "707701d6dcf5529a67cc9b1e300d5e1a273b9a7b02d91154d4a2a1978cd8e549"
"canonical": "{\"behaviour\":{\"acknowledgedBoundary\":\"42\",\"agents\":[{\"agentId\":\"fly-a\",\"brainTicks\":\"2517\",\"committedStep\":\"42\",\"decisionDigest\":\"a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b\",\"profileDigest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\",\"remainder\":{\"denominator\":\"3\",\"numerator\":\"1000000\"},\"ticksAdvanced\":\"17\"},{\"agentId\":\"fly-b\",\"brainTicks\":\"2550\",\"committedStep\":\"42\",\"decisionDigest\":\"ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e\",\"profileDigest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\",\"remainder\":{\"denominator\":\"1\",\"numerator\":\"0\"},\"ticksAdvanced\":\"17\"}],\"batchId\":\"batch-41\",\"boundaryActions\":[],\"controlDigest\":\"1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6\",\"eventIds\":[\"evt-1\"],\"observationBoundaries\":[{\"producedStep\":\"42\",\"viewId\":\"screen\"}],\"outcomeIds\":[\"outcome-1\"],\"publishedBoundary\":\"42\",\"scope\":{\"epoch\":\"epoch-1\",\"sessionId\":\"demo\",\"step\":\"41\"}},\"operational\":{\"advanceRequestId\":\"req-43\",\"busCallIds\":[\"call-100\",\"call-101\",\"call-102\"],\"captures\":[],\"commitRequestIds\":[{\"agentId\":\"fly-a\",\"requestId\":\"req-44\"},{\"agentId\":\"fly-b\",\"requestId\":\"req-45\"}],\"deliveryIds\":[\"dlv-7\",\"own-9\"],\"prepareRequestIds\":[{\"agentId\":\"fly-a\",\"requestId\":\"req-41\"},{\"agentId\":\"fly-b\",\"requestId\":\"req-42\"}],\"wallTimeNs\":\"1234567890\"}}",
"digest": "36ecd5d39f81863c30c0fd52a12d72eb3ba71f0f3b72b2349e2c7f02a1d8e3e5"
},
{
"name": "trace behaviour on its own",
@ -2853,11 +2855,12 @@
"eventIds": [
"evt-1"
],
"publishedBoundary": "42"
"publishedBoundary": "42",
"boundaryActions": []
},
"note": "the half two runs must agree on",
"canonical": "{\"acknowledgedBoundary\":\"42\",\"agents\":[{\"agentId\":\"fly-a\",\"brainTicks\":\"2517\",\"committedStep\":\"42\",\"decisionDigest\":\"a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b\",\"profileDigest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\",\"remainder\":{\"denominator\":\"3\",\"numerator\":\"1000000\"},\"ticksAdvanced\":\"17\"},{\"agentId\":\"fly-b\",\"brainTicks\":\"2550\",\"committedStep\":\"42\",\"decisionDigest\":\"ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e\",\"profileDigest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\",\"remainder\":{\"denominator\":\"1\",\"numerator\":\"0\"},\"ticksAdvanced\":\"17\"}],\"batchId\":\"batch-41\",\"controlDigest\":\"1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6\",\"eventIds\":[\"evt-1\"],\"observationBoundaries\":[{\"producedStep\":\"42\",\"viewId\":\"screen\"}],\"outcomeIds\":[\"outcome-1\"],\"publishedBoundary\":\"42\",\"scope\":{\"epoch\":\"epoch-1\",\"sessionId\":\"demo\",\"step\":\"41\"}}",
"digest": "d938188fe3006537dbc0038fabf2a05df584e64b361b1106d79f3f965dff414a"
"canonical": "{\"acknowledgedBoundary\":\"42\",\"agents\":[{\"agentId\":\"fly-a\",\"brainTicks\":\"2517\",\"committedStep\":\"42\",\"decisionDigest\":\"a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b\",\"profileDigest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\",\"remainder\":{\"denominator\":\"3\",\"numerator\":\"1000000\"},\"ticksAdvanced\":\"17\"},{\"agentId\":\"fly-b\",\"brainTicks\":\"2550\",\"committedStep\":\"42\",\"decisionDigest\":\"ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e\",\"profileDigest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\",\"remainder\":{\"denominator\":\"1\",\"numerator\":\"0\"},\"ticksAdvanced\":\"17\"}],\"batchId\":\"batch-41\",\"boundaryActions\":[],\"controlDigest\":\"1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6\",\"eventIds\":[\"evt-1\"],\"observationBoundaries\":[{\"producedStep\":\"42\",\"viewId\":\"screen\"}],\"outcomeIds\":[\"outcome-1\"],\"publishedBoundary\":\"42\",\"scope\":{\"epoch\":\"epoch-1\",\"sessionId\":\"demo\",\"step\":\"41\"}}",
"digest": "59ed2ef812fe44a5808d8251b253c76a642cdc6828ee964a1c90ef302b66c9e4"
},
{
"name": "trace operational metadata on its own",
@ -2893,11 +2896,12 @@
"deliveryIds": [
"dlv-7",
"own-9"
]
],
"captures": []
},
"note": "recorded by step-v1 section 8, excluded from its comparison",
"canonical": "{\"advanceRequestId\":\"req-43\",\"busCallIds\":[\"call-100\",\"call-101\",\"call-102\"],\"commitRequestIds\":[{\"agentId\":\"fly-a\",\"requestId\":\"req-44\"},{\"agentId\":\"fly-b\",\"requestId\":\"req-45\"}],\"deliveryIds\":[\"dlv-7\",\"own-9\"],\"prepareRequestIds\":[{\"agentId\":\"fly-a\",\"requestId\":\"req-41\"},{\"agentId\":\"fly-b\",\"requestId\":\"req-42\"}],\"wallTimeNs\":\"1234567890\"}",
"digest": "07c3402ab0e48b186d56f667da980a15b2a48b21381235cff37523a24ff0dfff"
"canonical": "{\"advanceRequestId\":\"req-43\",\"busCallIds\":[\"call-100\",\"call-101\",\"call-102\"],\"captures\":[],\"commitRequestIds\":[{\"agentId\":\"fly-a\",\"requestId\":\"req-44\"},{\"agentId\":\"fly-b\",\"requestId\":\"req-45\"}],\"deliveryIds\":[\"dlv-7\",\"own-9\"],\"prepareRequestIds\":[{\"agentId\":\"fly-a\",\"requestId\":\"req-41\"},{\"agentId\":\"fly-b\",\"requestId\":\"req-42\"}],\"wallTimeNs\":\"1234567890\"}",
"digest": "d9fd215dbea34237559ff5a5826beca16d3646340f2ce76ee62115d21f7c27d6"
},
{
"name": "agent telemetry reporting a running stimulation pulse",
@ -3338,12 +3342,39 @@
"mode": "macros",
"macroChannels": [
"macro_go_objective",
"macro_go_out",
"macro_go_warp",
"macro_go_route",
"macro_go_item",
"macro_go_npc",
"macro_go_frontier",
"macro_go_shop",
"macro_go_heal",
"macro_talk",
"macro_menu",
"macro_next",
"macro_move_1"
"macro_yes",
"macro_no",
"macro_close",
"macro_confirm",
"macro_back",
"macro_move_1",
"macro_move_2",
"macro_move_3",
"macro_move_4",
"macro_switch",
"macro_item",
"macro_throw_ball",
"macro_run",
"macro_buy_potion",
"macro_buy_ball",
"macro_buy_antidote",
"macro_buy_repel",
"macro_heal",
"macro_leave"
]
},
"decoderConfigDigest": "5c3f2f084bef674fed492b82e5e0951e993c856ae98f5453d180cd06143dc854",
"decoderConfigDigest": "82b6601f0390b742a67eca44ef935b49e16a73315db70f5644f1cbd21f7c8a52",
"environment": {
"extensions": [
"gameboy-slots-v1"
@ -3376,9 +3407,9 @@
"checkpointFormatOfRecord": "FLYSIM01",
"flysimCompatibility": "lif-1ms-f64-v2/pokered-unique8-v6/75ba5d3536a2862fdb9f4ef1a76b96fe099a7201ac737f0cf53fe4c5ad4183f3:1657ba7716494c9db95a13b1527bc129226363f99b395774de1f76ff28571f0e:63b1acb26272edccdcfacf3c2ff58069e0cefaa84451429c989258bafaeae1d5:f567d7f07227e71c0df2ab4e6510f3a3f79e51b675095792e16d216d74b7b5b7:ece0b5e76d1884dd2f3ff6e0362bc284febe205ac1ce07fdab5009942ddffb62:b8c33144d4cec31c3ac4b6091ef1f4207f567c4f710f7c13fd2dc47c44c2b634:dbbafc044cd50aad7b792615988ff6d9991c846cc3d8b2eafc86b7c357b5eefc/fly-kc-mbon-rstdp-v2/binjgb:c60e138da5a795ebb55e56b11b7e90024e41112c/pokered:0cd19d3b877b7dc66d12c7050bed9a7f38154d4b/statefmt:199616-x86_64-unknown-linux-gnu"
},
"note": "placeholder ROM and decoder digests",
"canonical": "{\"checkpointFormatOfRecord\":\"FLYSIM01\",\"compositionId\":\"pokered-live\",\"decoderConfigDigest\":\"5c3f2f084bef674fed492b82e5e0951e993c856ae98f5453d180cd06143dc854\",\"environment\":{\"audio\":{\"channels\":2,\"sampleRate\":48000},\"controllerSchema\":{\"digest\":\"1bde5fa114b99824ad608fba4ea85706cb0cebc6123a778dc1e5f89791d2a05e\",\"id\":\"gameboy-joypad-v1\",\"version\":1},\"extensions\":[\"gameboy-slots-v1\"],\"inspectionSchema\":{\"digest\":\"d6cb62248bfdac2ffdf00290ffbebf9a101b1fe28f7be766d24db28ede8da3e6\",\"id\":\"gameboy-memory-inspection-v1\",\"version\":1},\"setupFrames\":1,\"slots\":[\"best\"],\"stepDuration\":{\"denominator\":\"512\",\"numerator\":\"8572265625\"}},\"episodePolicy\":\"legacy-ratchet-rollback-v1\",\"executor\":{\"adapter\":\"pokered-unique8-v6\",\"id\":\"pokered-macros-v1\",\"macroChannels\":[\"macro_go_objective\",\"macro_talk\",\"macro_next\",\"macro_move_1\"],\"mode\":\"macros\",\"rom\":{\"byteLength\":\"1048576\",\"digest\":\"c840ea493f9bf41505f26cf5b1db26815dd588e7ec91d5fd6f1ad4d363dc4f20\",\"format\":\"gb-rom\",\"id\":\"pokered-rom\"},\"symbolProvenance\":\"0cd19d3b877b7dc66d12c7050bed9a7f38154d4b\"},\"flysimCompatibility\":\"lif-1ms-f64-v2/pokered-unique8-v6/75ba5d3536a2862fdb9f4ef1a76b96fe099a7201ac737f0cf53fe4c5ad4183f3:1657ba7716494c9db95a13b1527bc129226363f99b395774de1f76ff28571f0e:63b1acb26272edccdcfacf3c2ff58069e0cefaa84451429c989258bafaeae1d5:f567d7f07227e71c0df2ab4e6510f3a3f79e51b675095792e16d216d74b7b5b7:ece0b5e76d1884dd2f3ff6e0362bc284febe205ac1ce07fdab5009942ddffb62:b8c33144d4cec31c3ac4b6091ef1f4207f567c4f710f7c13fd2dc47c44c2b634:dbbafc044cd50aad7b792615988ff6d9991c846cc3d8b2eafc86b7c357b5eefc/fly-kc-mbon-rstdp-v2/binjgb:c60e138da5a795ebb55e56b11b7e90024e41112c/pokered:0cd19d3b877b7dc66d12c7050bed9a7f38154d4b/statefmt:199616-x86_64-unknown-linux-gnu\",\"profile\":{\"byteLength\":\"1137\",\"digest\":\"41e5d1ac62ab23f1b2d7252d52faac08b269c85e6b4c9ed7a370c74032c60878\",\"format\":\"fly-profile-v1\",\"id\":\"gameboy-legacy-fafb-v783-v1\"},\"restore\":\"legacy-transient-reset\",\"scheduler\":\"lockstep-v1\"}",
"digest": "f0142c7f09a1319453b472cdddbc1855062af5733f89d1fbc0b82c0adb52b0c7"
"note": "placeholder ROM digest; real macros-mode decoder digest and channels",
"canonical": "{\"checkpointFormatOfRecord\":\"FLYSIM01\",\"compositionId\":\"pokered-live\",\"decoderConfigDigest\":\"82b6601f0390b742a67eca44ef935b49e16a73315db70f5644f1cbd21f7c8a52\",\"environment\":{\"audio\":{\"channels\":2,\"sampleRate\":48000},\"controllerSchema\":{\"digest\":\"1bde5fa114b99824ad608fba4ea85706cb0cebc6123a778dc1e5f89791d2a05e\",\"id\":\"gameboy-joypad-v1\",\"version\":1},\"extensions\":[\"gameboy-slots-v1\"],\"inspectionSchema\":{\"digest\":\"d6cb62248bfdac2ffdf00290ffbebf9a101b1fe28f7be766d24db28ede8da3e6\",\"id\":\"gameboy-memory-inspection-v1\",\"version\":1},\"setupFrames\":1,\"slots\":[\"best\"],\"stepDuration\":{\"denominator\":\"512\",\"numerator\":\"8572265625\"}},\"episodePolicy\":\"legacy-ratchet-rollback-v1\",\"executor\":{\"adapter\":\"pokered-unique8-v6\",\"id\":\"pokered-macros-v1\",\"macroChannels\":[\"macro_go_objective\",\"macro_go_out\",\"macro_go_warp\",\"macro_go_route\",\"macro_go_item\",\"macro_go_npc\",\"macro_go_frontier\",\"macro_go_shop\",\"macro_go_heal\",\"macro_talk\",\"macro_menu\",\"macro_next\",\"macro_yes\",\"macro_no\",\"macro_close\",\"macro_confirm\",\"macro_back\",\"macro_move_1\",\"macro_move_2\",\"macro_move_3\",\"macro_move_4\",\"macro_switch\",\"macro_item\",\"macro_throw_ball\",\"macro_run\",\"macro_buy_potion\",\"macro_buy_ball\",\"macro_buy_antidote\",\"macro_buy_repel\",\"macro_heal\",\"macro_leave\"],\"mode\":\"macros\",\"rom\":{\"byteLength\":\"1048576\",\"digest\":\"c840ea493f9bf41505f26cf5b1db26815dd588e7ec91d5fd6f1ad4d363dc4f20\",\"format\":\"gb-rom\",\"id\":\"pokered-rom\"},\"symbolProvenance\":\"0cd19d3b877b7dc66d12c7050bed9a7f38154d4b\"},\"flysimCompatibility\":\"lif-1ms-f64-v2/pokered-unique8-v6/75ba5d3536a2862fdb9f4ef1a76b96fe099a7201ac737f0cf53fe4c5ad4183f3:1657ba7716494c9db95a13b1527bc129226363f99b395774de1f76ff28571f0e:63b1acb26272edccdcfacf3c2ff58069e0cefaa84451429c989258bafaeae1d5:f567d7f07227e71c0df2ab4e6510f3a3f79e51b675095792e16d216d74b7b5b7:ece0b5e76d1884dd2f3ff6e0362bc284febe205ac1ce07fdab5009942ddffb62:b8c33144d4cec31c3ac4b6091ef1f4207f567c4f710f7c13fd2dc47c44c2b634:dbbafc044cd50aad7b792615988ff6d9991c846cc3d8b2eafc86b7c357b5eefc/fly-kc-mbon-rstdp-v2/binjgb:c60e138da5a795ebb55e56b11b7e90024e41112c/pokered:0cd19d3b877b7dc66d12c7050bed9a7f38154d4b/statefmt:199616-x86_64-unknown-linux-gnu\",\"profile\":{\"byteLength\":\"1137\",\"digest\":\"41e5d1ac62ab23f1b2d7252d52faac08b269c85e6b4c9ed7a370c74032c60878\",\"format\":\"fly-profile-v1\",\"id\":\"gameboy-legacy-fafb-v783-v1\"},\"restore\":\"legacy-transient-reset\",\"scheduler\":\"lockstep-v1\"}",
"digest": "77d8a88ff7fea51eb29b1ae75fc9cf8c17c184e6a1e5a585e86399f41fd9c9b0"
},
{
"name": "an example legacy composition in raw mode",
@ -3405,7 +3436,7 @@
"mode": "raw",
"macroChannels": []
},
"decoderConfigDigest": "5c3f2f084bef674fed492b82e5e0951e993c856ae98f5453d180cd06143dc854",
"decoderConfigDigest": "6234e4a0363cfe82b8d515943c4f645a3960eda8fa5bf9465009061f80d50812",
"environment": {
"extensions": [
"gameboy-slots-v1"
@ -3439,8 +3470,225 @@
"flysimCompatibility": "lif-1ms-f64-v2/pokered-unique8-v6/75ba5d3536a2862fdb9f4ef1a76b96fe099a7201ac737f0cf53fe4c5ad4183f3:1657ba7716494c9db95a13b1527bc129226363f99b395774de1f76ff28571f0e:63b1acb26272edccdcfacf3c2ff58069e0cefaa84451429c989258bafaeae1d5:f567d7f07227e71c0df2ab4e6510f3a3f79e51b675095792e16d216d74b7b5b7:ece0b5e76d1884dd2f3ff6e0362bc284febe205ac1ce07fdab5009942ddffb62:b8c33144d4cec31c3ac4b6091ef1f4207f567c4f710f7c13fd2dc47c44c2b634:dbbafc044cd50aad7b792615988ff6d9991c846cc3d8b2eafc86b7c357b5eefc/fly-kc-mbon-rstdp-v2/binjgb:c60e138da5a795ebb55e56b11b7e90024e41112c/pokered:0cd19d3b877b7dc66d12c7050bed9a7f38154d4b/statefmt:199616-x86_64-unknown-linux-gnu"
},
"note": "",
"canonical": "{\"checkpointFormatOfRecord\":\"FLYSIM01\",\"compositionId\":\"pokered-live\",\"decoderConfigDigest\":\"5c3f2f084bef674fed492b82e5e0951e993c856ae98f5453d180cd06143dc854\",\"environment\":{\"audio\":{\"channels\":2,\"sampleRate\":48000},\"controllerSchema\":{\"digest\":\"1bde5fa114b99824ad608fba4ea85706cb0cebc6123a778dc1e5f89791d2a05e\",\"id\":\"gameboy-joypad-v1\",\"version\":1},\"extensions\":[\"gameboy-slots-v1\"],\"inspectionSchema\":{\"digest\":\"d6cb62248bfdac2ffdf00290ffbebf9a101b1fe28f7be766d24db28ede8da3e6\",\"id\":\"gameboy-memory-inspection-v1\",\"version\":1},\"setupFrames\":1,\"slots\":[\"best\"],\"stepDuration\":{\"denominator\":\"512\",\"numerator\":\"8572265625\"}},\"episodePolicy\":\"legacy-ratchet-rollback-v1\",\"executor\":{\"adapter\":\"pokered-unique8-v6\",\"id\":\"pokered-macros-v1\",\"macroChannels\":[],\"mode\":\"raw\",\"rom\":{\"byteLength\":\"1048576\",\"digest\":\"c840ea493f9bf41505f26cf5b1db26815dd588e7ec91d5fd6f1ad4d363dc4f20\",\"format\":\"gb-rom\",\"id\":\"pokered-rom\"},\"symbolProvenance\":\"0cd19d3b877b7dc66d12c7050bed9a7f38154d4b\"},\"flysimCompatibility\":\"lif-1ms-f64-v2/pokered-unique8-v6/75ba5d3536a2862fdb9f4ef1a76b96fe099a7201ac737f0cf53fe4c5ad4183f3:1657ba7716494c9db95a13b1527bc129226363f99b395774de1f76ff28571f0e:63b1acb26272edccdcfacf3c2ff58069e0cefaa84451429c989258bafaeae1d5:f567d7f07227e71c0df2ab4e6510f3a3f79e51b675095792e16d216d74b7b5b7:ece0b5e76d1884dd2f3ff6e0362bc284febe205ac1ce07fdab5009942ddffb62:b8c33144d4cec31c3ac4b6091ef1f4207f567c4f710f7c13fd2dc47c44c2b634:dbbafc044cd50aad7b792615988ff6d9991c846cc3d8b2eafc86b7c357b5eefc/fly-kc-mbon-rstdp-v2/binjgb:c60e138da5a795ebb55e56b11b7e90024e41112c/pokered:0cd19d3b877b7dc66d12c7050bed9a7f38154d4b/statefmt:199616-x86_64-unknown-linux-gnu\",\"profile\":{\"byteLength\":\"1137\",\"digest\":\"41e5d1ac62ab23f1b2d7252d52faac08b269c85e6b4c9ed7a370c74032c60878\",\"format\":\"fly-profile-v1\",\"id\":\"gameboy-legacy-fafb-v783-v1\"},\"restore\":\"legacy-transient-reset\",\"scheduler\":\"lockstep-v1\"}",
"digest": "efd699cf8cf2485dd2ac8a84ff43c9bc3711a78181f32285ed31a2ee86e16925"
"canonical": "{\"checkpointFormatOfRecord\":\"FLYSIM01\",\"compositionId\":\"pokered-live\",\"decoderConfigDigest\":\"6234e4a0363cfe82b8d515943c4f645a3960eda8fa5bf9465009061f80d50812\",\"environment\":{\"audio\":{\"channels\":2,\"sampleRate\":48000},\"controllerSchema\":{\"digest\":\"1bde5fa114b99824ad608fba4ea85706cb0cebc6123a778dc1e5f89791d2a05e\",\"id\":\"gameboy-joypad-v1\",\"version\":1},\"extensions\":[\"gameboy-slots-v1\"],\"inspectionSchema\":{\"digest\":\"d6cb62248bfdac2ffdf00290ffbebf9a101b1fe28f7be766d24db28ede8da3e6\",\"id\":\"gameboy-memory-inspection-v1\",\"version\":1},\"setupFrames\":1,\"slots\":[\"best\"],\"stepDuration\":{\"denominator\":\"512\",\"numerator\":\"8572265625\"}},\"episodePolicy\":\"legacy-ratchet-rollback-v1\",\"executor\":{\"adapter\":\"pokered-unique8-v6\",\"id\":\"pokered-macros-v1\",\"macroChannels\":[],\"mode\":\"raw\",\"rom\":{\"byteLength\":\"1048576\",\"digest\":\"c840ea493f9bf41505f26cf5b1db26815dd588e7ec91d5fd6f1ad4d363dc4f20\",\"format\":\"gb-rom\",\"id\":\"pokered-rom\"},\"symbolProvenance\":\"0cd19d3b877b7dc66d12c7050bed9a7f38154d4b\"},\"flysimCompatibility\":\"lif-1ms-f64-v2/pokered-unique8-v6/75ba5d3536a2862fdb9f4ef1a76b96fe099a7201ac737f0cf53fe4c5ad4183f3:1657ba7716494c9db95a13b1527bc129226363f99b395774de1f76ff28571f0e:63b1acb26272edccdcfacf3c2ff58069e0cefaa84451429c989258bafaeae1d5:f567d7f07227e71c0df2ab4e6510f3a3f79e51b675095792e16d216d74b7b5b7:ece0b5e76d1884dd2f3ff6e0362bc284febe205ac1ce07fdab5009942ddffb62:b8c33144d4cec31c3ac4b6091ef1f4207f567c4f710f7c13fd2dc47c44c2b634:dbbafc044cd50aad7b792615988ff6d9991c846cc3d8b2eafc86b7c357b5eefc/fly-kc-mbon-rstdp-v2/binjgb:c60e138da5a795ebb55e56b11b7e90024e41112c/pokered:0cd19d3b877b7dc66d12c7050bed9a7f38154d4b/statefmt:199616-x86_64-unknown-linux-gnu\",\"profile\":{\"byteLength\":\"1137\",\"digest\":\"41e5d1ac62ab23f1b2d7252d52faac08b269c85e6b4c9ed7a370c74032c60878\",\"format\":\"fly-profile-v1\",\"id\":\"gameboy-legacy-fafb-v783-v1\"},\"restore\":\"legacy-transient-reset\",\"scheduler\":\"lockstep-v1\"}",
"digest": "ac42e702ec6c9b09482b1ee335743ef85fe599a72e2c34a4d32aea67d67d0201"
},
{
"name": "a rank climb: slot saved, then the milestone capture",
"type": "TransitionTrace",
"value": {
"behaviour": {
"scope": {
"sessionId": "demo",
"epoch": "epoch-1",
"step": "41"
},
"agents": [
{
"agentId": "fly-a",
"profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0",
"ticksAdvanced": "17",
"brainTicks": "2517",
"remainder": {
"numerator": "1000000",
"denominator": "3"
},
"decisionDigest": "a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b",
"committedStep": "42"
},
{
"agentId": "fly-b",
"profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0",
"ticksAdvanced": "17",
"brainTicks": "2550",
"remainder": {
"numerator": "0",
"denominator": "1"
},
"decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e",
"committedStep": "42"
}
],
"batchId": "batch-41",
"controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6",
"acknowledgedBoundary": "42",
"observationBoundaries": [
{
"viewId": "screen",
"producedStep": "42"
}
],
"outcomeIds": [
"outcome-1"
],
"eventIds": [
"evt-1"
],
"publishedBoundary": "42",
"boundaryActions": [
{
"kind": "save-slot",
"slotId": "best",
"stateDigest": "5d41402abc4b2a76b9719d911017c592ae9d3a0c8a7a6f5d2e3d4c5b6a7f8e9d"
}
]
},
"operational": {
"wallTimeNs": "1234567890",
"prepareRequestIds": [
{
"agentId": "fly-a",
"requestId": "req-41"
},
{
"agentId": "fly-b",
"requestId": "req-42"
}
],
"advanceRequestId": "req-43",
"commitRequestIds": [
{
"agentId": "fly-a",
"requestId": "req-44"
},
{
"agentId": "fly-b",
"requestId": "req-45"
}
],
"busCallIds": [
"call-100",
"call-101",
"call-102"
],
"deliveryIds": [
"dlv-7",
"own-9"
],
"captures": [
{
"checkpointId": "milestone-11",
"afterActions": 1
}
]
}
},
"note": "legacy-gameboy-v1 section 16: a slot save due at a boundary completes before any capture there",
"canonical": "{\"behaviour\":{\"acknowledgedBoundary\":\"42\",\"agents\":[{\"agentId\":\"fly-a\",\"brainTicks\":\"2517\",\"committedStep\":\"42\",\"decisionDigest\":\"a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b\",\"profileDigest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\",\"remainder\":{\"denominator\":\"3\",\"numerator\":\"1000000\"},\"ticksAdvanced\":\"17\"},{\"agentId\":\"fly-b\",\"brainTicks\":\"2550\",\"committedStep\":\"42\",\"decisionDigest\":\"ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e\",\"profileDigest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\",\"remainder\":{\"denominator\":\"1\",\"numerator\":\"0\"},\"ticksAdvanced\":\"17\"}],\"batchId\":\"batch-41\",\"boundaryActions\":[{\"kind\":\"save-slot\",\"slotId\":\"best\",\"stateDigest\":\"5d41402abc4b2a76b9719d911017c592ae9d3a0c8a7a6f5d2e3d4c5b6a7f8e9d\"}],\"controlDigest\":\"1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6\",\"eventIds\":[\"evt-1\"],\"observationBoundaries\":[{\"producedStep\":\"42\",\"viewId\":\"screen\"}],\"outcomeIds\":[\"outcome-1\"],\"publishedBoundary\":\"42\",\"scope\":{\"epoch\":\"epoch-1\",\"sessionId\":\"demo\",\"step\":\"41\"}},\"operational\":{\"advanceRequestId\":\"req-43\",\"busCallIds\":[\"call-100\",\"call-101\",\"call-102\"],\"captures\":[{\"afterActions\":1,\"checkpointId\":\"milestone-11\"}],\"commitRequestIds\":[{\"agentId\":\"fly-a\",\"requestId\":\"req-44\"},{\"agentId\":\"fly-b\",\"requestId\":\"req-45\"}],\"deliveryIds\":[\"dlv-7\",\"own-9\"],\"prepareRequestIds\":[{\"agentId\":\"fly-a\",\"requestId\":\"req-41\"},{\"agentId\":\"fly-b\",\"requestId\":\"req-42\"}],\"wallTimeNs\":\"1234567890\"}}",
"digest": "68c6f1e21cc6f63c36e69b73dbd5c11313b2a76e57f1e5e8468984516a6f96d5"
},
{
"name": "save, capture, rollback, capture at one boundary",
"type": "TransitionTrace",
"value": {
"behaviour": {
"scope": {
"sessionId": "demo",
"epoch": "epoch-1",
"step": "41"
},
"agents": [
{
"agentId": "fly-a",
"profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0",
"ticksAdvanced": "17",
"brainTicks": "2517",
"remainder": {
"numerator": "1000000",
"denominator": "3"
},
"decisionDigest": "a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b",
"committedStep": "42"
},
{
"agentId": "fly-b",
"profileDigest": "1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0",
"ticksAdvanced": "17",
"brainTicks": "2550",
"remainder": {
"numerator": "0",
"denominator": "1"
},
"decisionDigest": "ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e",
"committedStep": "42"
}
],
"batchId": "batch-41",
"controlDigest": "1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6",
"acknowledgedBoundary": "42",
"observationBoundaries": [
{
"viewId": "screen",
"producedStep": "42"
}
],
"outcomeIds": [
"outcome-1"
],
"eventIds": [
"evt-1"
],
"publishedBoundary": "42",
"boundaryActions": [
{
"kind": "save-slot",
"slotId": "best",
"stateDigest": "5d41402abc4b2a76b9719d911017c592ae9d3a0c8a7a6f5d2e3d4c5b6a7f8e9d"
},
{
"kind": "rollback",
"slotId": "best",
"stateDigest": null
}
]
},
"operational": {
"wallTimeNs": "1234567890",
"prepareRequestIds": [
{
"agentId": "fly-a",
"requestId": "req-41"
},
{
"agentId": "fly-b",
"requestId": "req-42"
}
],
"advanceRequestId": "req-43",
"commitRequestIds": [
{
"agentId": "fly-a",
"requestId": "req-44"
},
{
"agentId": "fly-b",
"requestId": "req-45"
}
],
"busCallIds": [
"call-100",
"call-101",
"call-102"
],
"deliveryIds": [
"dlv-7",
"own-9"
],
"captures": [
{
"checkpointId": "periodic-7",
"afterActions": 1
},
{
"checkpointId": "after-rollback-7",
"afterActions": 2
}
]
}
},
"note": "a durable save follows the rollback, as the legacy loop checkpoints after a recovery",
"canonical": "{\"behaviour\":{\"acknowledgedBoundary\":\"42\",\"agents\":[{\"agentId\":\"fly-a\",\"brainTicks\":\"2517\",\"committedStep\":\"42\",\"decisionDigest\":\"a62db30eb7427e2ea9ec58661e69a1eff38e67f21ec97df7d91b24015890d89b\",\"profileDigest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\",\"remainder\":{\"denominator\":\"3\",\"numerator\":\"1000000\"},\"ticksAdvanced\":\"17\"},{\"agentId\":\"fly-b\",\"brainTicks\":\"2550\",\"committedStep\":\"42\",\"decisionDigest\":\"ddc2e7b8b300cda681c3d91368050fcb95dd66d20fd295834c31307d3df2623e\",\"profileDigest\":\"1900eab6c028483d7126599ee6f50de0d27907b5c65fa90524580b4b0f9852b0\",\"remainder\":{\"denominator\":\"1\",\"numerator\":\"0\"},\"ticksAdvanced\":\"17\"}],\"batchId\":\"batch-41\",\"boundaryActions\":[{\"kind\":\"save-slot\",\"slotId\":\"best\",\"stateDigest\":\"5d41402abc4b2a76b9719d911017c592ae9d3a0c8a7a6f5d2e3d4c5b6a7f8e9d\"},{\"kind\":\"rollback\",\"slotId\":\"best\",\"stateDigest\":null}],\"controlDigest\":\"1e2135d1b50f14d3d3d6cbe24a4294bcb9e4d5d3c28eb1ce4980ff2a17c5d3b6\",\"eventIds\":[\"evt-1\"],\"observationBoundaries\":[{\"producedStep\":\"42\",\"viewId\":\"screen\"}],\"outcomeIds\":[\"outcome-1\"],\"publishedBoundary\":\"42\",\"scope\":{\"epoch\":\"epoch-1\",\"sessionId\":\"demo\",\"step\":\"41\"}},\"operational\":{\"advanceRequestId\":\"req-43\",\"busCallIds\":[\"call-100\",\"call-101\",\"call-102\"],\"captures\":[{\"afterActions\":1,\"checkpointId\":\"periodic-7\"},{\"afterActions\":2,\"checkpointId\":\"after-rollback-7\"}],\"commitRequestIds\":[{\"agentId\":\"fly-a\",\"requestId\":\"req-44\"},{\"agentId\":\"fly-b\",\"requestId\":\"req-45\"}],\"deliveryIds\":[\"dlv-7\",\"own-9\"],\"prepareRequestIds\":[{\"agentId\":\"fly-a\",\"requestId\":\"req-41\"},{\"agentId\":\"fly-b\",\"requestId\":\"req-42\"}],\"wallTimeNs\":\"1234567890\"}}",
"digest": "ae22a2c5e3eb5e65c6ca82c27b2f5242bd94983fca4b11978d7067dc4389d0bb"
}
]
}

View file

@ -126,6 +126,11 @@ pub const ENUMS: &[EnumSchema] = &[
source: "publishing-v1 3",
members: &["lockstep-v1"],
},
EnumSchema {
name: "BoundaryActionKind",
source: "step-v1 8",
members: crate::trace::BoundaryActionKind::ALL,
},
EnumSchema {
name: "EpisodeRequestKind",
source: "workers-v1 4",
@ -1070,6 +1075,11 @@ pub const SCHEMAS: &[TypeSchema] = &[
req("outcomeIds", "array<Id>", "task outcome ids in task order"),
req("eventIds", "array<Id>", "task event ids in task order"),
req("publishedBoundary", "U64", ""),
req(
"boundaryActions",
"array<{kind:BoundaryActionKind,slotId:Id,stateDigest:Digest|null}>",
"<= 5, application order: slot saves first (each slot once, digest set), then at most one rollback (no digest)",
),
],
},
TypeSchema {
@ -1103,6 +1113,11 @@ pub const SCHEMAS: &[TypeSchema] = &[
),
req("busCallIds", "array<BusCallId>", "call-<U64>"),
req("deliveryIds", "array<OwnerToken>", "dlv-<U64> or own-<U64>"),
req(
"captures",
"array<{checkpointId:Id,afterActions:int}>",
"taken order, unique checkpointId; afterActions >= the boundary's slot saves and <= its actions",
),
],
},
];

View file

@ -44,6 +44,42 @@ pub struct TraceObservation {
pub produced_step: u64,
}
/// The kind of an action taken at the boundary a transition reached, after all of its commits
/// (amendment of 2026-09-23, RT-01a).
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BoundaryActionKind {
SaveSlot,
Rollback,
}
impl BoundaryActionKind {
pub const ALL: &'static [&'static str] = &["save-slot", "rollback"];
pub fn as_str(self) -> &'static str {
match self {
BoundaryActionKind::SaveSlot => "save-slot",
BoundaryActionKind::Rollback => "rollback",
}
}
pub fn parse(s: &str) -> Result<BoundaryActionKind> {
match s {
"save-slot" => Ok(BoundaryActionKind::SaveSlot),
"rollback" => Ok(BoundaryActionKind::Rollback),
_ => err("boundary action kind must be save-slot or rollback"),
}
}
}
/// One boundary action, in the order the coordinator applied it: an `Environment.SaveSlot`
/// (with the saved state's digest) or a rollback to a slot (no digest).
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BoundaryAction {
pub kind: BoundaryActionKind,
pub slot_id: String,
pub state_digest: Option<String>,
}
/// The fields two runs of the same transition must agree on.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TraceBehaviour {
@ -60,6 +96,9 @@ pub struct TraceBehaviour {
/// Task event ids in task order.
pub event_ids: Vec<String>,
pub published_boundary: u64,
/// Slot saves and a rollback at the reached boundary, in application order. Empty for a
/// composition without them.
pub boundary_actions: Vec<BoundaryAction>,
}
impl TraceBehaviour {
@ -126,6 +165,22 @@ impl DomainType for TraceBehaviour {
let outcome_ids = crate::scalar::id_list(&mut f, "outcomeIds", 0, MAX_RATE_ROLES)?;
let event_ids = crate::scalar::id_list(&mut f, "eventIds", 0, MAX_RATE_ROLES)?;
let published_boundary = f.u64_string("publishedBoundary")?;
let boundary_actions = list(&mut f, "boundaryActions", 0, MAX_BOUNDARY_ACTIONS, |v| {
let mut a = Fields::new(v, "TraceBehaviour.boundaryActions")?;
let kind = BoundaryActionKind::parse(a.string("kind")?)?;
let slot_id = a.id("slotId")?;
let state_digest = match a.value("stateDigest")? {
Value::Null => None,
Value::String(d) => Some(d.clone()),
_ => return err("stateDigest must be null or a digest"),
};
a.finish()?;
Ok(BoundaryAction {
kind,
slot_id,
state_digest,
})
})?;
f.finish()?;
let b = TraceBehaviour {
scope,
@ -137,6 +192,7 @@ impl DomainType for TraceBehaviour {
outcome_ids,
event_ids,
published_boundary,
boundary_actions,
};
b.validate()?;
Ok(b)
@ -190,6 +246,24 @@ impl DomainType for TraceBehaviour {
Value::Array(self.event_ids.iter().map(|i| i.clone().into()).collect()),
),
("publishedBoundary", u64_json(self.published_boundary)),
(
"boundaryActions",
Value::Array(
self.boundary_actions
.iter()
.map(|a| {
obj(vec![
("kind", a.kind.as_str().into()),
("slotId", a.slot_id.clone().into()),
(
"stateDigest",
a.state_digest.clone().map_or(Value::Null, Value::String),
),
])
})
.collect(),
),
),
])
}
@ -244,8 +318,55 @@ impl DomainType for TraceBehaviour {
self.outcome_ids.iter().map(String::as_str),
"TraceBehaviour.outcomeIds",
)?;
self.validate_boundary_actions()
}
}
/// Boundary actions per transition: every slot at most once, plus one rollback.
pub const MAX_BOUNDARY_ACTIONS: usize = crate::extensions::MAX_SLOTS + 1;
impl TraceBehaviour {
/// Slot saves come first, each slot at most once, with the saved state's digest; at most
/// one rollback, last, naming a slot and no digest (step-v1 section 6 amendment).
fn validate_boundary_actions(&self) -> Result<()> {
let mut rolled_back = false;
let mut saved: Vec<&str> = Vec::new();
for action in &self.boundary_actions {
if !is_id(&action.slot_id) {
return err("TraceBehaviour: boundary action slotId is not a valid id");
}
if rolled_back {
return err("TraceBehaviour: nothing follows a rollback at the same boundary");
}
match action.kind {
BoundaryActionKind::SaveSlot => {
match &action.state_digest {
Some(d) if is_digest(d) => {}
_ => return err("TraceBehaviour: a slot save records its state digest"),
}
if saved.contains(&action.slot_id.as_str()) {
return err("TraceBehaviour: a slot is saved at most once per boundary");
}
saved.push(&action.slot_id);
}
BoundaryActionKind::Rollback => {
if action.state_digest.is_some() {
return err("TraceBehaviour: a rollback records no state digest");
}
rolled_back = true;
}
}
}
Ok(())
}
/// How many leading boundary actions are slot saves.
pub fn slot_saves(&self) -> usize {
self.boundary_actions
.iter()
.take_while(|a| a.kind == BoundaryActionKind::SaveSlot)
.count()
}
}
/// One agent's domain request id for one phase.
@ -267,6 +388,18 @@ pub struct TraceOperational {
/// these and nothing in [`TraceBehaviour`].
pub bus_call_ids: Vec<BusCallId>,
pub delivery_ids: Vec<OwnerToken>,
/// Checkpoint captures (and FLYSIM01 exports) taken at the reached boundary, each with the
/// number of boundary actions already applied when it was taken. Operational because a
/// capture's schedule is wall-clock policy; the ordering rule against slot saves is checked
/// by [`TransitionTrace`] (amendment of 2026-09-23, legacy-gameboy-v1 section 16).
pub captures: Vec<TraceCapture>,
}
/// One checkpoint capture at the reached boundary.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TraceCapture {
pub checkpoint_id: String,
pub after_actions: u64,
}
impl DomainType for TraceOperational {
@ -296,6 +429,16 @@ impl DomainType for TraceOperational {
Some(s) => OwnerToken::parse(s),
None => err("every deliveryId must be a string"),
})?;
let captures = list(&mut f, "captures", 0, MAX_BOUNDARY_ACTIONS + 1, |v| {
let mut c = Fields::new(v, "TraceOperational.captures")?;
let checkpoint_id = c.id("checkpointId")?;
let after_actions = c.int("afterActions", 0, MAX_BOUNDARY_ACTIONS as u64)?;
c.finish()?;
Ok(TraceCapture {
checkpoint_id,
after_actions,
})
})?;
f.finish()?;
let o = TraceOperational {
wall_time_ns,
@ -304,6 +447,7 @@ impl DomainType for TraceOperational {
commit_request_ids,
bus_call_ids,
delivery_ids,
captures,
};
o.validate()?;
Ok(o)
@ -341,6 +485,20 @@ impl DomainType for TraceOperational {
.collect(),
),
),
(
"captures",
Value::Array(
self.captures
.iter()
.map(|c| {
obj(vec![
("checkpointId", c.checkpoint_id.clone().into()),
("afterActions", Value::from(c.after_actions)),
])
})
.collect(),
),
),
])
}
@ -361,6 +519,17 @@ impl DomainType for TraceOperational {
self.delivery_ids.iter().map(OwnerToken::as_str),
"TraceOperational.deliveryIds",
)?;
require_unique(
self.captures.iter().map(|c| c.checkpoint_id.as_str()),
"TraceOperational.captures",
)?;
let mut last = 0;
for capture in &self.captures {
if capture.after_actions < last {
return err("TraceOperational: captures are recorded in the order they were taken");
}
last = capture.after_actions;
}
Ok(())
}
}
@ -412,6 +581,9 @@ impl TransitionTrace {
if a.event_ids != b.event_ids {
out.push("eventIds differ".to_owned());
}
if a.boundary_actions != b.boundary_actions {
out.push("boundaryActions differ".to_owned());
}
let ids_a: Vec<&str> = a.agents.iter().map(|x| x.agent_id.as_str()).collect();
let ids_b: Vec<&str> = b.agents.iter().map(|x| x.agent_id.as_str()).collect();
if ids_a != ids_b {
@ -481,6 +653,25 @@ impl DomainType for TransitionTrace {
}
}
}
// A slot save due at a boundary completes before any capture at that boundary, so a
// checkpoint never pairs a task ledger that names the new slot with the old slot
// contents (legacy-gameboy-v1 section 16, review round 1 of 2026-09-23).
let saves = self.behaviour.slot_saves() as u64;
let actions = self.behaviour.boundary_actions.len() as u64;
for capture in &self.operational.captures {
if capture.after_actions < saves {
return err(format!(
"TransitionTrace: capture {:?} was taken before this boundary's slot saves completed",
capture.checkpoint_id
));
}
if capture.after_actions > actions {
return err(format!(
"TransitionTrace: capture {:?} counts more boundary actions than were applied",
capture.checkpoint_id
));
}
}
Ok(())
}
}

View file

@ -2659,6 +2659,8 @@ impl Coordinator {
outcome_ids,
event_ids: event_ids.to_vec(),
published_boundary: k + 1,
// The synthetic composition takes no boundary actions (step-v1 section 8 amendment).
boundary_actions: Vec::new(),
};
let operational = TraceOperational {
// Wall time is for pacing, health and presentation only.
@ -2673,6 +2675,9 @@ impl Coordinator {
// and nothing in the behaviour above.
bus_call_ids: Vec::new(),
delivery_ids: Vec::new(),
// Captures are recorded by the store path, not by the transition that reached the
// boundary; the synthetic trace records none.
captures: Vec::new(),
};
self.trace.transition(TransitionTrace { behaviour, operational });
}