Building a tournament
A tournament is built by four writes, in a client's order: addParticipants, addEvent, addEventEntries
and addDrawDefinition (a draw generated by generateDrawDefinition, added as it was generated), with seven
smaller writes around them (§6: venues and courts, policies and extensions, flights, scale items, publication). This
page states what each stores, byte for byte, so that a second implementation can reproduce the record they leave: the
inputs, the refusals, every field written, and the stamps. Where the behaviour is a question rather than a decision it
is marked OPEN.
First version, 2026-10-29, written from the source; §6 added 2026-10-30. The golden corpus pins it: the mocks source
(pnpm corpus:mocks) writes generated tournaments as these calls (generateTournamentRecord({ recordMethods: true })),
with the state hash after every call. A record is hashed in canonical form (RFC 8785), so key order never matters;
a key holding undefined is not stored.
1. What every call shares
1.1 The clock and the stamps
An engine call captures one instant when it starts, and every write in the call reads it: an executionQueue shares
one instant across all its directives. Two kinds of stamp are written from it.
updatedAt (ISO string) is stamped on a drawDefinition, its top-level structures and its matchUps, by the
notices that announce them, never on participants, events, entries or flights. The rule:
tis the captured instant. If the entity's existingupdatedAtis at or aftert, the new value is the existing value plus 1 ms; otherwise it ist.- An entity is stamped at most once per call (the first stamp stands).
So a draw generated in one call and added in the next, under the same instant, is stored one millisecond later than
its generation stamp, and a matchUp that had no stamp is stored at t.
The factory stamp on the tournament record, factory: { ...existing, version, timeStamp }, is written after a
call on each record the call modified, and only on those: a record is modified by a notice that names its
tournamentId (whether or not anyone subscribes); a notice that names none modifies every loaded record; a notice that
only reports (dataIssue, audit) modifies nothing. version is the package version; timeStamp is the captured
instant in epoch milliseconds (a number). A write made with suppressNotifications is stamped as an announced one is:
its draws, structures and matchUps take updatedAt, and its record the factory stamp; only the notices are dropped. A
call that writes nothing, or writes only to a working copy (the engine's internal disableNotifications), does not
stamp. A refused call without rollback keeps its earlier writes and is stamped if any of them modified a record; with
rollbackOnError nothing is kept or stamped. (A state provider without the optional cycleModifiedTournamentIds hook
stamps every loaded record, and one without markModified leaves a suppressed write unstamped.)
1.2 Ids and randomness
An id a write mints (a participant's, an event's, a TEAM collection's) is a v4-style UUID drawn from the call's random
source: a seeded call (nonRandom) draws from a stream derived from its seed, otherwise from the engine's configured
source. A client that supplies its ids makes no draws. Every call in the corpus supplies them.
The mint. An id takes four draws r0..r3 from the source (setRandomSource(seed) installs mulberry32 seeded
with seed). Each is scaled and truncated to a 32-bit word, di = trunc(ri × 0xffffffff), and the id is the words'
bytes, least significant first, as two lowercase hex digits each, in the 8-4-4-4-12 form:
d0[0] d0[1] d0[2] d0[3] - d1[0] d1[1] - (d1[2] & 0x0f | 0x40) d1[3] - (d2[0] & 0x3f | 0x80) d2[1] - d2[2] d2[3] d3[0] d3[1] d3[2] d3[3]
where dn[k] is byte k of dn ((dn >> 8k) & 0xff): the version nibble is 4 and the variant bits are 10. With a
prefix, the id is `${prefix}_${id without hyphens}`. A source that returns a value outside [0, 1), or that
mints an id it minted among its recent ids, throws (DEGENERATE_RANDOM_SOURCE).
1.3 Params
The engine copies the params object shallowly, then resolves the record, the event and the draw the call works on. An engine call works only on the records the engine holds: whether the call names them by id or passes them as objects, it is given the ones in state, never the caller's own. This is done for every call, before the method runs:
- An entity given both as an object and by id must be one entity: a
tournamentRecordwhosetournamentId, adrawDefinitionwhosedrawIdor aneventwhoseeventIdis not the id given beside it refuses the call (INVALID_VALUES). - A
tournamentRecordpassed is resolved by itstournamentId. AtournamentId(given, or the passed record's) that names no loaded record refuses the call:MISSING_TOURNAMENT_RECORD. - A
drawDefinitionpassed is resolved by itsdrawId. AdrawId(or amatchUp'sdrawId) found in a loaded record gives the call that draw, its event and its record; a flight can name its event after its draw is gone. A passed draw found nowhere refuses the call (DRAW_DEFINITION_NOT_FOUND); adrawIdfound nowhere, given alone, resolves nothing and the method decides. - Otherwise an
eventpassed is resolved by itseventId, and aneventIdgives the call the stored event and its record. An event with noeventId, or aneventIdfound in no loaded record, refuses the call:EVENT_NOT_FOUND. - The record the call writes is the one resolved, else the one
tournamentIdnames, else the active record. AneventIdordrawIdheld by another loaded record moves the call to that record; the active record is unchanged.
A method's creation payload is not resolved: addEvent's event and addDrawDefinition's drawDefinition are the
entities it stores. To work on a record, draw or event the engine does not hold (a record being built before it is
loaded, a generated draw before it is added), call the governor function directly, which works on the object given.
Called directly, a function given both an entity and its id is held to rule 1.
Nested objects are not copied: an object passed in (a participant, a drawDefinition) is the object stored, and a write to it after the call changes the record.
2. addParticipants
Inputs. participants (default []), allowDuplicateParticipantIdPairs, returnParticipants.
Refusals, in order, the first refusal ending the call. A participant is checked against those appended before it in the same call, so a refusal can come part way through; the participants appended before it are then removed, and the call writes nothing:
- No tournament:
MISSING_TOURNAMENT_RECORD. - Then per participant, in the add order below:
- its
participantIdis already in the record, including one appended earlier in the same call:PARTICIPANT_ID_EXISTS; participantTypenot one of INDIVIDUAL, PAIR, TEAM, GROUP:INVALID_PARTICIPANT_TYPE;- no
participantRole:MISSING_PARTICIPANT_ROLE; - a
personon anything but an INDIVIDUAL, orindividualParticipantIdsthat is not an array:INVALID_VALUES; - PAIR: no
individualParticipantIds(MISSING_PARTICIPANT_IDS), not exactly two, or one not an INDIVIDUAL in the record (INVALID_PARTICIPANT_IDS); - INDIVIDUAL: a
person.sexthat is not FEMALE, MALE, OTHER, F, M or O (INVALID_VALUES); no name, meaning neither both standard names norparticipantOtherNamenorparticipantName(MISSING_PERSON_DETAILS); - TEAM or GROUP: a member id that is not a string (
INVALID_VALUES) or not an INDIVIDUAL in the record (PARTICIPANT_NOT_FOUND).
- its
What is written.
participantsis created on the record if absent (an empty array when the call adds no one); a refused call creates nothing.- A participant without a
participantIdis given one, before anything else, in input order. - Participants whose id was in the record when the call began are skipped (reported in
notAdded). The rest are appended in this order: every INDIVIDUAL in input order, then every other participant in input order. Each is the object passed in. participantName, when given, has its whitespace collapsed (runs of whitespace become one space, ends trimmed).- INDIVIDUAL:
person.sex''ornullis removed; F, M, O and the long forms are stored as FEMALE, MALE, OTHER.person.standardGivenNameandstandardFamilyNamehave their whitespace collapsed. A missingparticipantNamebecomes"<standardGivenName> <standardFamilyName>"when both are present, otherwiseparticipantOtherName ?? ''. - PAIR: if a PAIR of the same two individuals exists (the first in record order), nothing is written for it, unless
allowDuplicateParticipantIdPairs. A missingparticipantNameis built from its individuals in record order: each one'sstandardFamilyName, elseparticipantOtherName, elseparticipantName; nameless ones dropped; a FEMALE and MALE pair puts the female first, otherwise names sort (Intl.Collator('en')); joined with/; one name alone becomes"<name>/Unknown". - TEAM, GROUP: a
genderis normalised (F, M, X, A to FEMALE, MALE, MIXED, ANY);individualParticipantIdsdefaults to[]. - No timestamps. One notice for the call, if anything was added.
Returns { success, addedCount }, with participants (copies) when returnParticipants, and notAdded with
info: EXISTING_PARTICIPANT when some were skipped. When nothing new is given: { success, addedCount: 0, info }.
3. addEvent
Inputs. event (it may omit eventId), suppressNotifications, uuids (a pool for TEAM collection ids).
internalUse, which lets an event arrive with entries or draws, is not a client param.
Refusals, in order:
- No tournament or no
event:MISSING_TOURNAMENT_RECORD,MISSING_EVENT(INVALID_VALUESwhen not an object). - The event has entries or draws:
INVALID_VALUES,info: 'entries/drawDefinitions cannot exist'(empty arrays pass). - TEAM with a
tieFormatthat does not validate:INVALID_TIE_FORMAT. TEAM with atieFormatNamethat is not a named format (fixtures.tieFormats, which lists everytieFormatConstantsname):INVALID_VALUES.STANDARD, the builder's fallback for a name it does not know, is not a named format. - The
eventId(given or minted) is already an event's:EVENT_EXISTS.
What is written. A new object, appended to events (created on the record if absent):
{ eventType: 'SINGLES', drawDefinitions: [], entries: [], startDate: record.startDate, endDate: record.endDate, ...event }
- The caller's fields override the defaults, an explicit
undefinedincluded. Nested objects are stored as given. gender, when truthy: F, M, X, A (and the long forms) to FEMALE, MALE, MIXED, ANY, exactly and case-sensitively; anything else is stored as given.discipline, when a non-empty string: trimmed, upper-cased, runs of whitespace or-replaced by_.- TEAM with a
tieFormat: a copy is stored, each collection without acollectionIdgiven one (fromuuids, taken from the end, else minted), in order. TEAM with only atieFormatName: the named format is stored astieFormatandtieFormatNamestays; each collection id is taken fromuuids(from the end), else minted, or"<eventId>-COL-<n>"(the event's stored id) when the tournament or event is a mock. eventId, when absent, is minted, before any collection ids.- No timestamps on the event. The event's draws and their matchUps (none, for an event added before its draws) are
stamped (§1.1), and, unless
suppressNotifications, announced, whether or not anything subscribes.
Returns { success, event } (the stored event).
4. addEventEntries
Inputs. eventId (or a drawId, which also names the event), participantIds, entryStatus
(DIRECT_ACCEPTANCE), entryStage (MAIN), entryStageSequence, autoEntryPositions (true), roundTarget,
extension / extensions, enforceGender (true), enforceCategory (false), and for a draw
suppressDuplicateEntries (true) and ignoreStageSpace.
Refusals. Nothing is written before any of them; the call is all or nothing for the event.
-
participantIdsnot an array:INVALID_PARTICIPANT_IDS. No event:MISSING_EVENT. No ids after removing duplicates:MISSING_PARTICIPANT_IDS. An event withouteventId:EVENT_NOT_FOUND. A malformed extension:INVALID_VALUES. -
Eligibility. A participant must be a competitor (no
participantRole, orCOMPETITOR) of the event's kind:- SINGLES: an INDIVIDUAL, entered with a status that is not UNGROUPED or UNPAIRED;
- DOUBLES: a PAIR, or an INDIVIDUAL entered UNGROUPED or UNPAIRED;
- TEAM: a TEAM, or an INDIVIDUAL entered UNGROUPED or UNPAIRED;
- HYBRID: an INDIVIDUAL or a PAIR.
Where the event has a
genderother than MIXED or ANY and gender is enforced, an individual'sperson.sexmust match it. WithenforceCategory, every individual must fit the event's category. Any participant failing these:INVALID_PARTICIPANT_IDS, withcontextnamingmismatchedGenderandcategoryRejections.
What is written.
entriesis created on the event if absent. Each id not already entered gets, in input order:{ participantId, entryStatus, entryStage }, plusextensions(copies), anextension(withcreatedAtset to the call's instant if it has none),roundTargetandentryStageSequencewhen given. Existing entries are unchanged.- With
autoEntryPositions, the event's entries are then rewritten: grouped by stage and status (groups in order of first appearance, which can reorder the array), ordered within a group by their existingentryPosition(absent last, stable), and numberedentryPosition1..n per group. - DOUBLES and TEAM: individuals entered UNGROUPED or UNPAIRED who now belong to an entered PAIR or TEAM are removed from the event's entries (unless one holds a draw position).
- With a
drawId, the same participants are entered in that draw (and its flight), ungrouped individuals included; a refusal there is returned asinfoand the event's entries stand. A participant already in the draw's entries is not entered again (reported in the draw'sduplicateEntries, and refused withDUPLICATE_ENTRYwhensuppressDuplicateEntriesis false). The draw and its structures are stamped (§1.1). - No stamps on the event or its entries.
Returns { success, addedEntriesCount, removedEntriesCount }, with info when the draw refused.
5. addDrawDefinition
Inputs. eventId, drawDefinition (complete: structures, matchUps, entries, positions), suppressNotifications,
existingDrawCount, allowReplacement, force, modifyEventEntries, checkEntryStatus, flight.
Refusals, in order; nothing is written before any of them:
- No draw:
MISSING_DRAW_DEFINITION. No event:MISSING_EVENT. existingDrawCountgiven and not the event's draw count:INVALID_VALUES.- The event's flight for this draw names a source draw that is not in the event:
MISSING_DRAW_DEFINITION. - The selected flight (
flight.flightNumber) is for another draw, or a draw entry is not among its entries:INVALID_DRAW_DEFINITION. WithcheckEntryStatus, a draw entry without a matching event entry:INVALID_DRAW_DEFINITION. - The event already has this
drawId:DRAW_ID_EXISTS, unlessallowReplacement; replacing a draw with scores needsforceor the scoring policy's permission (SCORES_PRESENT).
What is written, in this order:
drawDefinitionsis created on the event if absent.- With
modifyEventEntries, each event entry whose participant is selected in the draw takes the draw entry's status. drawOrder= one more than the largest of the event's draws'drawOrders and its flights'flightNumbers (0 when there are none).- The flight. If the event's
flightProfilehas a flight for thisdrawId, itsdrawNameis set from the draw, and itsflightNumber(a number, or a numeric string read as its number) becomes the draw'sdrawOrderwhen it is not already used; the flight then takesdrawOrderas itsflightNumber. Otherwise a flight is appended (the profile created if absent):{ manuallyAdded: true, flightNumber: drawOrder, drawEntries, drawName, drawId }, itsdrawEntriesa copy of the draw's entries. drawOrderis set on the draw.- A draw without its stored
winnerMatchUpId/loserMatchUpIdedges has them written (a generated draw has them). - A new draw is appended to the event's
drawDefinitions(the object passed in). Every matchUp in it is stamped, then the draw and its top-level structures (§1.1), and, unlesssuppressNotifications, announced. A replacement takes the old draw's place, stamped the same way.
Returns { success, modifiedEventEntryStatusCount }.
6. The smaller writes
Seven calls that a generated tournament makes around the four above: venues and courts, policies and extensions on the
record, flights, seeding (scale items) and publication. None of them stamps updatedAt except attachPolicies on a
draw; each raises the notice named, whether or not anything subscribes, and so takes the factory stamp (§1.1).
6.1 addVenue
Inputs. venue, context, disableNotice. The venue goes to every loaded record (one, unless tournamentId
names it).
venuenot an object:INVALID_VALUES.venueIdis minted if absent, onto a copy (the caller's object is not written).- A venue with this
venueIdexists:VENUE_EXISTS.defaultStartTimewithoutdefaultEndTime(or the reverse), or an end not after the start:INVALID_VALUES.
What is written. A non-empty dateAvailability is canonicalized (times to HH:MM, a truthy date to
YYYY-MM-DD), as are defaultStartTime and defaultEndTime. context becomes an extension
{ name: 'context', value, createdAt: now }. A venue added with isPrimary clears isPrimary on the record's other
venues. Each record stores its own copy of the venue (courts as given), venues created if absent; a refusal writes
nothing. Notice ADD_VENUE unless disableNotice.
Returns { success, venue }.
6.2 addCourts
Inputs. venueId, courtsCount, courtNames, courtIds, dateAvailability, courtTimings, dates,
startTime, endTime, courtNameRoot (default Court), venueAbbreviationRoot, idPrefix.
Refusals. venueId not a non-empty string: MISSING_VENUE_ID. No loaded record holds the venue:
VENUE_NOT_FOUND. courtsCount not numeric and no courtNames: MISSING_COURTS_INFO. A court that cannot be added
(an id the venue or the call already holds, an invalid availability): INVALID_VALUES
(not all courts could be generated). Every court is built before any is stored, so a refusal writes nothing.
What is written, for court i of courtsCount (a number or a numeric string, else courtNames.length), in every
record holding the venue:
- Availability:
dateAvailability(default[]) shared by every court, unlesscourtTimings[i]is given; thendates.map((date) => ({ date, startTime, endTime, ...courtTimings[i] })), with{ startTime, endTime }appended when both are given. - Name:
courtNames[i], else`${venue.venueAbbreviation} ${i + 1}`whenvenueAbbreviationRootis given and the venue has an abbreviation, else`${courtNameRoot} ${i + 1}`. - Id: the next of
courtIds(taken from the front), else`${idPrefix}-${i + 1}`, else minted. courtsis created on the venue if absent, and the court is appended:{ courtId, courtName, dateAvailability, onlineResources: [], venueId }. Each availability entry is copied, itsdate(when truthy) and times converted to their stored forms, and its bookings rebuilt as{ startTime, endTime, bookingType }.
Notice MODIFY_VENUE. Returns { success, courtIds }.
6.3 attachPolicies
Inputs. policyDefinitions (an object keyed by policy type), allowReplacement, and a draw (drawId), an event
(eventId) or a tournamentId. The element is the draw, else the event, else the named record; with none of them,
every loaded record. A drawId that resolves to no draw refuses (DRAW_DEFINITION_NOT_FOUND), as an unresolved
eventId does (EVENT_NOT_FOUND). A round robin tally policy is validated first.
What is written, on each element:
extensionsis created if absent.- The applied policies are a copy of the element's
appliedPoliciesextension value. For each type given: a falsy policy is skipped; a policy that is not an object, holds nothing but itspolicyName(an attribute whose value isundefineddoes not count: it is not stored), or has apolicyNamethat is not a string refuses (INVALID_VALUES); a type already held is skipped unlessallowReplacement; otherwise the policy replaces that type whole. - If any type was taken, the
appliedPoliciesextension's value is replaced, or the extension is appended:{ name: 'appliedPolicies', value, createdAt: now }.
A draw is announced as modified (stamping it and its structures, §1.1); an event or a record is announced as modified.
Across every loaded record, a record that takes nothing is passed over. Nothing taken anywhere: EXISTING_POLICY_TYPE.
Returns { success, applied } (the types taken).
6.4 addTournamentExtension
Inputs. extension ({ name, value }), creationTime. A name that is not a string, or an extension without a
name or value key, refuses.
extensions is created on the record if absent. The stored extension is a copy carrying createdAt (the caller's, else
now; none with creationTime: false). An extension of the same name has its value replaced; otherwise the copy is
appended, only if its value is truthy. Notice MODIFY_TOURNAMENT_DETAIL if the extensions changed.
Returns { success }.
6.5 addFlight
Inputs. eventId, drawId, drawName, drawEntries, stage, qualifyingPositions.
Refusals, in order, before any write: no event, MISSING_EVENT; no drawName, MISSING_VALUE; a draw entry whose
participant is not among the event's entries, INVALID_VALUES; the profile has a flight with this drawId,
EXISTING_FLIGHT.
What is written. flightNumber = one more than the largest numeric flightNumber in the event's flight profile
(0 when none). The flight is { drawId, flightNumber, drawEntries, drawName } (drawId minted if absent,
drawEntries default [], stored as passed), with stage and qualifyingPositions only when truthy. The event's
flightProfile becomes { ...profile, flights: [...flights, flight] } (a profile held as an extension is moved to the
attribute). Notice MODIFY_EVENT. Returns { success }.
6.6 setParticipantScaleItem
Inputs. participantId, scaleItem ({ scaleType, eventType, scaleName, scaleValue, scaleDate, scaleId, createdAt }), removePriorValues.
Refusals. scaleItem without the keys scaleType, eventType and scaleName: INVALID_SCALE_ITEM. No such
participant: PARTICIPANT_NOT_FOUND. A TEAM participant with an eventType other than TEAM, or any of the three
keys falsy: INVALID_SCALE_ITEM. A changed value with a scaleDate that is not a date: INVALID_DATE, after
timeItems is created.
What is written. itemType is `SCALE.${scaleType}.${eventType}.${scaleName}`; timeItems is created on the
participant if absent. The value is unchanged when it equals the participant's current item of that type
(compared as JSON; undefined, null and '' are equal to each other): nothing more is written. Otherwise the time
item { itemValue: scaleValue, itemDate: scaleDate, createdAt: scaleItem.createdAt ?? now, itemType } (undefined keys
dropped), with itemSubTypes: [scaleId] when scaleId is truthy, is appended; removePriorValues first removes the
items of that type. Notice MODIFY_PARTICIPANTS when the value changed.
Returns { success, newValue }, or { success, info: VALUE_UNCHANGED, existingValue }.
6.7 publishEvent
Inputs. eventId, drawIds, drawDetails, eventDataParams, embargo, status (default PUBLIC),
removePriorValues (default true).
Refusals. No event: MISSING_EVENT. A drawId not in the event: DRAW_DEFINITION_NOT_FOUND. An embargo that is
not a date: INVALID_EMBARGO. A structure not in its draw: STRUCTURE_NOT_FOUND.
What is written. A time item on the event:
{ itemType: 'PUBLISH.STATUS', createdAt: now, itemValue: { ...otherStatuses, [status]: { ...prior, drawDetails } } }.
A plain publication gives every draw of the event, in order, { [drawId]: { publishingDetail: { published: true } } }
(an event without draws gives {}). timeItems is created if absent; with removePriorValues the event's earlier
PUBLISH.STATUS items are removed; the item is appended. Notice PUBLISH_EVENT (its payload carries the event data
only for a subscriber).
Returns { success, eventData }.
7. Guarantees
- A refusal of
addParticipants,addEvent,addEventEntriesoraddDrawDefinitionwrites nothing. - Given the same record, params, clock and random source, each call stores the same bytes.
8. Open questions
Resolved 2026-10-30: a replacement with suppressNotifications now stores the new draw (it sat inside the
notification guard); addEvent stamps an event's matchUps whether or not anything subscribes (it stamped them only for
a subscriber, so the stored bytes depended on who was listening).
Resolved 2026-10-30, the smaller writes (§6): publishEvent, setParticipantScaleItem and eleven other participant
writes announce whether or not anything subscribes, so the factory stamp no longer depends on who is listening;
addCourts counts a numeric string as its number and refuses before storing any court; attachPolicies refuses a
drawId that resolves to no draw (it attached to every loaded record); addVenue and addTournamentExtension store
copies and no longer write the caller's objects, and addVenue creates venues only when it stores one.
Resolved 2026-10-30, the four writes: addParticipants is all or nothing (it kept the participants appended before a
refusal, unannounced); addParticipants and addEvent create no empty participants or events on a refused
call; addEvent mints the eventId before a named tieFormat's collection ids, which take uuids and, for a mock,
the stored id (they were undefined-COL-n); a new flight's drawEntries is a copy of the draw's entries (it was
the same array); a numeric string flightNumber is read as its number (it became a string drawOrder).
addEventEntries tested isUngrouped(entryStage), a stage against statuses, which never held: the dead test is
removed and the behaviour it never changed is stated (§4: every entry made with a drawId reaches the draw,
ungrouped individuals included, as an AD_HOC doubles field relies on).
Resolved 2026-10-30 (debate-ruled, two opposed measurements each, pending review): a draw entry made under the
default suppressDuplicateEntries for a participant already in the draw was reported as a duplicate and written
anyway (a probe draw then refused automated positioning); it is now skipped, as addDrawEntry and the flight path
skip it. INTENNSE_2026 had a constant, a template and a JSON but no fixtures.tieFormats entry, so addEvent alone
refused it; it is now published, and STANDARD (the fallback, not a named format) is still refused.
Resolved 2026-10-30 (CA): the factory stamp goes only to the records a call modified (it went to every loaded record,
so a record merely loaded beside a written one claimed to have been written); a notice that only reports
(dataIssue, audit) modifies nothing, and addDrawEntries announces only when the entries changed. A write made with
suppressNotifications is stamped as an announced one is (it left its draws and matchUps unstamped and the factory
stamp unmoved); only the engine's internal disableNotifications, around working copies, stamps nothing.
Decided (CA 2026-10-30): an engine call works only on the records it holds (§1.3). A caller's own event object
was the event the method wrote to: addEventEntries given a copy of the stored event (or an event in no loaded record)
reported success, wrote the entries into the caller's object and left the record unchanged, even beside a correct
eventId. A tournamentRecord and a drawDefinition passed through the engine behaved the same way. Now every object
passed is resolved to the one in state, one not held refuses the call, and an object and an id that name different
entities refuse it, with the engine or without it. A write never silently goes nowhere. (Recorded 2026-10-29 as a
refusal at 8.0.0; CA brought it forward.)
Resolved (CA 2026-10-29): a policy attribute whose value is undefined does not count toward a policy (§6.3). It was
counted, so { policyName, roundsToSeparate: undefined } was accepted and stored as its name alone, which the same
check refuses.
No question is open.
Related
- Concepts: the scheduling pipeline, the same treatment for scheduling.
- The golden corpus (
corpus/README.mdin the repository):pnpm corpus:mocks.