@ttt-productions/q-core 0.1.20 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (196) hide show
  1. package/dist/constants/business.js +2 -2
  2. package/dist/constants/business.js.map +1 -1
  3. package/dist/constants/media.js +1 -1
  4. package/dist/constants/media.js.map +1 -1
  5. package/dist/constants/sports.js +1 -1
  6. package/dist/constants/sports.js.map +1 -1
  7. package/dist/constants/telemetry.js +3 -3
  8. package/dist/constants/telemetry.js.map +1 -1
  9. package/dist/doc-schemas/admin-notifications.js +3 -3
  10. package/dist/doc-schemas/admin-notifications.js.map +1 -1
  11. package/dist/doc-schemas/aggregate-stats.js +3 -3
  12. package/dist/doc-schemas/aggregate-stats.js.map +1 -1
  13. package/dist/doc-schemas/atoms.d.ts +1 -1
  14. package/dist/doc-schemas/atoms.js +3 -3
  15. package/dist/doc-schemas/atoms.js.map +1 -1
  16. package/dist/doc-schemas/audit-event.js +2 -2
  17. package/dist/doc-schemas/audit-event.js.map +1 -1
  18. package/dist/doc-schemas/firestore-primitives.js +1 -1
  19. package/dist/doc-schemas/firestore-primitives.js.map +1 -1
  20. package/dist/doc-schemas/game-location.js +2 -2
  21. package/dist/doc-schemas/game-location.js.map +1 -1
  22. package/dist/doc-schemas/game-stat-history.js +1 -1
  23. package/dist/doc-schemas/game-stat-history.js.map +1 -1
  24. package/dist/doc-schemas/game-stats.js +6 -6
  25. package/dist/doc-schemas/game-stats.js.map +1 -1
  26. package/dist/doc-schemas/league-team.js +4 -4
  27. package/dist/doc-schemas/league-team.js.map +1 -1
  28. package/dist/doc-schemas/membership-roster-sync.js +2 -2
  29. package/dist/doc-schemas/membership-roster-sync.js.map +1 -1
  30. package/dist/doc-schemas/pending-media.d.ts +6 -6
  31. package/dist/doc-schemas/publicUser.js +1 -1
  32. package/dist/doc-schemas/publicUser.js.map +1 -1
  33. package/dist/doc-schemas/registration-start.js +1 -1
  34. package/dist/doc-schemas/registration-start.js.map +1 -1
  35. package/dist/doc-schemas/registry.d.ts +3 -3
  36. package/dist/doc-schemas/sport-game.js +4 -4
  37. package/dist/doc-schemas/sport-game.js.map +1 -1
  38. package/dist/doc-schemas/sport-league.js +1 -1
  39. package/dist/doc-schemas/sport-league.js.map +1 -1
  40. package/dist/doc-schemas/stats-operation.js +2 -2
  41. package/dist/doc-schemas/stats-operation.js.map +1 -1
  42. package/dist/doc-schemas/status-reconcile-queue.js +1 -1
  43. package/dist/doc-schemas/status-reconcile-queue.js.map +1 -1
  44. package/dist/doc-schemas/sweep-state.js +2 -2
  45. package/dist/doc-schemas/sweep-state.js.map +1 -1
  46. package/dist/doc-schemas/team-membership.js +1 -1
  47. package/dist/doc-schemas/team-membership.js.map +1 -1
  48. package/dist/doc-schemas/user-profile.js +1 -1
  49. package/dist/doc-schemas/user-profile.js.map +1 -1
  50. package/dist/doc-schemas/waiver-template.js +1 -1
  51. package/dist/doc-schemas/waiver-template.js.map +1 -1
  52. package/dist/doc-schemas/waivers-and-hardware.js +3 -3
  53. package/dist/doc-schemas/waivers-and-hardware.js.map +1 -1
  54. package/dist/domain-events/domain-events.js +2 -2
  55. package/dist/domain-events/domain-events.js.map +1 -1
  56. package/dist/ids/award-grant-ids.js +1 -1
  57. package/dist/ids/award-grant-ids.js.map +1 -1
  58. package/dist/ids/compound-ids.js +1 -1
  59. package/dist/ids/compound-ids.js.map +1 -1
  60. package/dist/ids/minted-ids.js +1 -1
  61. package/dist/ids/minted-ids.js.map +1 -1
  62. package/dist/ids/notification-ids.js +1 -1
  63. package/dist/ids/notification-ids.js.map +1 -1
  64. package/dist/ids/stat-event-ids.d.ts +1 -1
  65. package/dist/ids/stat-event-ids.js +2 -2
  66. package/dist/ids/stat-event-ids.js.map +1 -1
  67. package/dist/ids/stats-operation-ids.js +1 -1
  68. package/dist/ids/stats-operation-ids.js.map +1 -1
  69. package/dist/index.js +1 -1
  70. package/dist/index.js.map +1 -1
  71. package/dist/input-formats/input-formats.js +1 -1
  72. package/dist/input-formats/input-formats.js.map +1 -1
  73. package/dist/media/file-origins.js +1 -1
  74. package/dist/media/file-origins.js.map +1 -1
  75. package/dist/media/picture-url.js +1 -1
  76. package/dist/media/picture-url.js.map +1 -1
  77. package/dist/migration-console/action-ids.js +1 -1
  78. package/dist/migration-console/action-ids.js.map +1 -1
  79. package/dist/migration-console/manifest.js +1 -1
  80. package/dist/migration-console/manifest.js.map +1 -1
  81. package/dist/notifications/admin-notification-config.js +1 -1
  82. package/dist/notifications/admin-notification-config.js.map +1 -1
  83. package/dist/notifications/occurrences.js +1 -1
  84. package/dist/notifications/occurrences.js.map +1 -1
  85. package/dist/paths/collections.js +3 -3
  86. package/dist/paths/collections.js.map +1 -1
  87. package/dist/paths/picture-owner-paths.js +1 -1
  88. package/dist/paths/picture-owner-paths.js.map +1 -1
  89. package/dist/paths/storage-paths.d.ts +1 -1
  90. package/dist/paths/storage-paths.js +1 -1
  91. package/dist/paths/storage-paths.js.map +1 -1
  92. package/dist/paths/utils.d.ts +1 -1
  93. package/dist/paths/utils.js +1 -1
  94. package/dist/paths/utils.js.map +1 -1
  95. package/dist/schemas/admin-notifications.js +1 -1
  96. package/dist/schemas/admin-notifications.js.map +1 -1
  97. package/dist/schemas/app-config.js +1 -1
  98. package/dist/schemas/app-config.js.map +1 -1
  99. package/dist/schemas/awards.js +1 -1
  100. package/dist/schemas/awards.js.map +1 -1
  101. package/dist/schemas/dead-letters.js +2 -2
  102. package/dist/schemas/dead-letters.js.map +1 -1
  103. package/dist/schemas/leagues.js +1 -1
  104. package/dist/schemas/leagues.js.map +1 -1
  105. package/dist/schemas/migration-console.js +1 -1
  106. package/dist/schemas/migration-console.js.map +1 -1
  107. package/dist/schemas/pictures.js +1 -1
  108. package/dist/schemas/pictures.js.map +1 -1
  109. package/dist/schemas/schedules.js +2 -2
  110. package/dist/schemas/schedules.js.map +1 -1
  111. package/dist/schemas/scoring.js +1 -1
  112. package/dist/schemas/scoring.js.map +1 -1
  113. package/dist/schemas/signed-waivers.js +1 -1
  114. package/dist/schemas/signed-waivers.js.map +1 -1
  115. package/dist/schemas/stats.js +6 -6
  116. package/dist/schemas/stats.js.map +1 -1
  117. package/dist/schemas/team-joining.js +3 -3
  118. package/dist/schemas/team-joining.js.map +1 -1
  119. package/dist/schemas/teams.js +7 -7
  120. package/dist/schemas/teams.js.map +1 -1
  121. package/dist/schemas/users.js +3 -3
  122. package/dist/schemas/users.js.map +1 -1
  123. package/dist/sports/basketball-scoring.js +4 -4
  124. package/dist/sports/basketball-scoring.js.map +1 -1
  125. package/dist/sports/bulk-entry.js +1 -1
  126. package/dist/sports/bulk-entry.js.map +1 -1
  127. package/dist/sports/contribution.d.ts +5 -5
  128. package/dist/sports/contribution.d.ts.map +1 -1
  129. package/dist/sports/contribution.js +4 -4
  130. package/dist/sports/contribution.js.map +1 -1
  131. package/dist/sports/curling-scoring.js +2 -2
  132. package/dist/sports/curling-scoring.js.map +1 -1
  133. package/dist/sports/football-scoring.js +2 -2
  134. package/dist/sports/football-scoring.js.map +1 -1
  135. package/dist/sports/index.js +1 -1
  136. package/dist/sports/index.js.map +1 -1
  137. package/dist/sports/ledger-continuity.js +1 -1
  138. package/dist/sports/ledger-continuity.js.map +1 -1
  139. package/dist/sports/registry.js +2 -2
  140. package/dist/sports/registry.js.map +1 -1
  141. package/dist/sports/replay.js +1 -1
  142. package/dist/sports/replay.js.map +1 -1
  143. package/dist/sports/rollup-check.js +1 -1
  144. package/dist/sports/rollup-check.js.map +1 -1
  145. package/dist/sports/score-mirror.js +1 -1
  146. package/dist/sports/score-mirror.js.map +1 -1
  147. package/dist/sports/scoring-display.d.ts +1 -1
  148. package/dist/sports/scoring-display.js +2 -2
  149. package/dist/sports/scoring-display.js.map +1 -1
  150. package/dist/sports/scoring.d.ts +5 -5
  151. package/dist/sports/scoring.d.ts.map +1 -1
  152. package/dist/sports/scoring.js +2 -2
  153. package/dist/sports/scoring.js.map +1 -1
  154. package/dist/sports/sport-adapter.d.ts +3 -3
  155. package/dist/sports/sport-adapter.d.ts.map +1 -1
  156. package/dist/sports/sport-adapter.js +2 -2
  157. package/dist/sports/sport-adapter.js.map +1 -1
  158. package/dist/sports/store-driver.d.ts +1 -1
  159. package/dist/sports/store-driver.d.ts.map +1 -1
  160. package/dist/sports/store-driver.js +2 -2
  161. package/dist/sports/store-driver.js.map +1 -1
  162. package/dist/types/audit.js +4 -4
  163. package/dist/types/audit.js.map +1 -1
  164. package/dist/types/claims.js +1 -1
  165. package/dist/types/claims.js.map +1 -1
  166. package/dist/types/picture-owners.js +1 -1
  167. package/dist/types/picture-owners.js.map +1 -1
  168. package/dist/types/schedules.js +1 -1
  169. package/dist/types/schedules.js.map +1 -1
  170. package/dist/types/sports.js +2 -2
  171. package/dist/types/sports.js.map +1 -1
  172. package/dist/types/stats.js +5 -5
  173. package/dist/types/stats.js.map +1 -1
  174. package/dist/types/unions.js +2 -2
  175. package/dist/types/unions.js.map +1 -1
  176. package/dist/types/uploads.js +1 -1
  177. package/dist/types/uploads.js.map +1 -1
  178. package/dist/upload-variables/sign-hardware-contract-variables.js +1 -1
  179. package/dist/upload-variables/sign-hardware-contract-variables.js.map +1 -1
  180. package/dist/upload-variables/sign-waiver-variables.js +1 -1
  181. package/dist/upload-variables/sign-waiver-variables.js.map +1 -1
  182. package/dist/utils/account-status.js +1 -1
  183. package/dist/utils/account-status.js.map +1 -1
  184. package/dist/utils/app-routes.js +1 -1
  185. package/dist/utils/app-routes.js.map +1 -1
  186. package/dist/utils/league-join-warning.js +1 -1
  187. package/dist/utils/league-join-warning.js.map +1 -1
  188. package/dist/utils/league-joining.js +1 -1
  189. package/dist/utils/league-joining.js.map +1 -1
  190. package/dist/utils/league-mode.js +1 -1
  191. package/dist/utils/league-mode.js.map +1 -1
  192. package/dist/utils/team-creation.js +1 -1
  193. package/dist/utils/team-creation.js.map +1 -1
  194. package/dist/utils/team-member-actions.js +1 -1
  195. package/dist/utils/team-member-actions.js.map +1 -1
  196. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"game-location.js","sourceRoot":"","sources":["../../src/doc-schemas/game-location.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,iGAAiG;AACjG,oBAAoB;AAEpB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,MAAM,CAAC,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;AAE5D,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE;IACzB,6CAA6C;IAC7C,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACjC,sDAAsD;IACtD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IAClC,8FAA8F;IAC9F,6CAA6C;IAC7C,SAAS,EAAE,eAAe,CAAC,QAAQ,EAAE;IACrC,SAAS,EAAE,aAAa;IACxB,SAAS,EAAE,aAAa;CACzB,CAAC,CAAC;AAGH,oGAAoG;AACpG,sGAAsG;AACtG,+FAA+F;AAC/F,qFAAqF;AACrF,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC,YAAY,CAAC;IACvD,gBAAgB,EAAE,WAAW;CAC9B,CAAC,CAAC","sourcesContent":["// `gameLocations/{gameLocationId}` — one venue games are played at. Games name it by id\n// (`gameLocationId`) beside their own free-text label, so a game still reads correctly after its\n// venue is deleted.\n\nimport { z } from 'zod';\nimport { countSchema, documentIdSegmentSchema, epochMsSchema } from './atoms.js';\nimport { pictureIdSchema } from './gallery.js';\n\nexport const gameLocationIdSchema = documentIdSegmentSchema;\n\nexport const GameLocationSchema = z.object({\n name: z.string().min(1),\n streetAddress: z.string(),\n // Courts, sheets, or rooms within the venue.\n subLocations: z.array(z.string()),\n // Directions for players; absent when there are none.\n memo: z.string().min(1).optional(),\n // The venue's picture. Only the picture pipeline writes it (ARCH-001); the location callables\n // write the fields above and never this one.\n pictureId: pictureIdSchema.optional(),\n createdAt: epochMsSchema,\n updatedAt: epochMsSchema,\n});\nexport type GameLocation = z.infer<typeof GameLocationSchema>;\n\n// `_serverData/gameLocationRegistry` — the fence of the venue cap. Venue creation counts the venues\n// in its transaction against MAX_GAME_LOCATIONS, and a query does not lock against an insert, so each\n// creation advances `locationRevision` here: two creations at the cap conflict instead of both\n// passing (BACKEND-116). Only its changing matters; absent until the first creation.\nexport const GameLocationRegistrySchema = z.strictObject({\n locationRevision: countSchema,\n});\nexport type GameLocationRegistry = z.infer<typeof GameLocationRegistrySchema>;\n"]}
1
+ {"version":3,"file":"game-location.js","sourceRoot":"","sources":["../../src/doc-schemas/game-location.ts"],"names":[],"mappings":"AAAA,wFAAwF;AACxF,iGAAiG;AACjG,oBAAoB;AAEpB,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,uBAAuB,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AACjF,OAAO,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAE/C,MAAM,CAAC,MAAM,oBAAoB,GAAG,uBAAuB,CAAC;AAE5D,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACvB,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE;IACzB,6CAA6C;IAC7C,YAAY,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC;IACjC,sDAAsD;IACtD,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE;IAClC,uGAAuG;IACvG,6CAA6C;IAC7C,SAAS,EAAE,eAAe,CAAC,QAAQ,EAAE;IACrC,SAAS,EAAE,aAAa;IACxB,SAAS,EAAE,aAAa;CACzB,CAAC,CAAC;AAGH,oGAAoG;AACpG,sGAAsG;AACtG,+FAA+F;AAC/F,8FAA8F;AAC9F,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC,CAAC,YAAY,CAAC;IACvD,gBAAgB,EAAE,WAAW;CAC9B,CAAC,CAAC","sourcesContent":["// `gameLocations/{gameLocationId}` — one venue games are played at. Games name it by id\n// (`gameLocationId`) beside their own free-text label, so a game still reads correctly after its\n// venue is deleted.\n\nimport { z } from 'zod';\nimport { countSchema, documentIdSegmentSchema, epochMsSchema } from './atoms.js';\nimport { pictureIdSchema } from './gallery.js';\n\nexport const gameLocationIdSchema = documentIdSegmentSchema;\n\nexport const GameLocationSchema = z.object({\n name: z.string().min(1),\n streetAddress: z.string(),\n // Courts, sheets, or rooms within the venue.\n subLocations: z.array(z.string()),\n // Directions for players; absent when there are none.\n memo: z.string().min(1).optional(),\n // The venue's picture. Only the picture pipeline writes it (RULE-STD-ARCH-001); the location callables\n // write the fields above and never this one.\n pictureId: pictureIdSchema.optional(),\n createdAt: epochMsSchema,\n updatedAt: epochMsSchema,\n});\nexport type GameLocation = z.infer<typeof GameLocationSchema>;\n\n// `_serverData/gameLocationRegistry` — the fence of the venue cap. Venue creation counts the venues\n// in its transaction against MAX_GAME_LOCATIONS, and a query does not lock against an insert, so each\n// creation advances `locationRevision` here: two creations at the cap conflict instead of both\n// passing (RULE-STD-BACKEND-116). Only its changing matters; absent until the first creation.\nexport const GameLocationRegistrySchema = z.strictObject({\n locationRevision: countSchema,\n});\nexport type GameLocationRegistry = z.infer<typeof GameLocationRegistrySchema>;\n"]}
@@ -4,7 +4,7 @@
4
4
  //
5
5
  // The first page also carries what finalize applied: the sport, the teams, and the participation
6
6
  // snapshot, so the record alone reproduces a completed game's contribution without the game
7
- // document a hand edit can reach (SPORT-204). It exists exactly while the game is `completed`.
7
+ // document a hand edit can reach (RULE-QS-SPORT-924). It exists exactly while the game is `completed`.
8
8
  import { z } from 'zod';
9
9
  import { STAT_HISTORY_PAGE_SIZE } from '../constants/sports.js';
10
10
  import { gameRosterPlayerIdSchema, leagueTypeSchema, teamIdSchema } from './atoms.js';
@@ -1 +1 @@
1
- {"version":3,"file":"game-stat-history.js","sourceRoot":"","sources":["../../src/doc-schemas/game-stat-history.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,qFAAqF;AACrF,6EAA6E;AAC7E,EAAE;AACF,iGAAiG;AACjG,4FAA4F;AAC5F,+FAA+F;AAE/F,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACtF,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEtD,MAAM,gBAAgB,GAAG,CAAC;KACvB,KAAK,CAAC,mBAAmB,CAAC;KAC1B,GAAG,CAAC,sBAAsB,CAAC;KAC3B,MAAM,CACL,CAAC,MAAM,EAAE,EAAE,CACT,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,EAC5F,4CAA4C,CAC7C,CAAC;AAEJ,yEAAyE;AACzE,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAAC,CAAC,YAAY,CAAC;IAC3D,MAAM,EAAE,gBAAgB;IACxB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACtC,UAAU,EAAE,gBAAgB;IAC5B,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5C,mDAAmD;IACnD,eAAe,EAAE,CAAC,CAAC,KAAK,CAAC,wBAAwB,CAAC;IAClD,qBAAqB,EAAE,CAAC,CAAC,KAAK,CAAC,wBAAwB,CAAC;IACxD,yBAAyB,EAAE,CAAC,CAAC,KAAK,CAAC,wBAAwB,CAAC;CAC7D,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,qCAAqC,GAAG,CAAC,CAAC,YAAY,CAAC;IAClE,MAAM,EAAE,gBAAgB,CAAC,MAAM,CAC7B,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAC7B,mCAAmC,CACpC;CACF,CAAC,CAAC;AAGH,mGAAmG;AACnG,+BAA+B;AAC/B,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC;IAC/C,8BAA8B;IAC9B,qCAAqC;CACtC,CAAC,CAAC","sourcesContent":["// `sportGames/{gameId}/gameStatHistory/{pageId}` — a completed game's finalized record: every event\n// document of the game, exactly as stored and in `sequence` order, in pages of up to\n// STAT_HISTORY_PAGE_SIZE, the page ids from `buildStatHistoryPageId(index)`.\n//\n// The first page also carries what finalize applied: the sport, the teams, and the participation\n// snapshot, so the record alone reproduces a completed game's contribution without the game\n// document a hand edit can reach (SPORT-204). It exists exactly while the game is `completed`.\n\nimport { z } from 'zod';\nimport { STAT_HISTORY_PAGE_SIZE } from '../constants/sports.js';\nimport { gameRosterPlayerIdSchema, leagueTypeSchema, teamIdSchema } from './atoms.js';\nimport { GameStatEventSchema } from './game-stats.js';\n\nconst pageEventsSchema = z\n .array(GameStatEventSchema)\n .max(STAT_HISTORY_PAGE_SIZE)\n .refine(\n (events) =>\n events.every((event, index) => index === 0 || events[index - 1].sequence < event.sequence),\n 'A page holds its events in sequence order.',\n );\n\n// A game whose replay is its empty stats has a first page and no events.\nexport const GameStatHistoryFirstPageSchema = z.strictObject({\n events: pageEventsSchema,\n pageCount: z.number().int().positive(),\n leagueType: leagueTypeSchema,\n teamIds: z.array(teamIdSchema).min(1).max(2),\n // The participation snapshot, as roster-entry ids.\n rosterPlayerIds: z.array(gameRosterPlayerIdSchema),\n absentRosterPlayerIds: z.array(gameRosterPlayerIdSchema),\n nonScoringRosterPlayerIds: z.array(gameRosterPlayerIdSchema),\n});\nexport type GameStatHistoryFirstPage = z.infer<typeof GameStatHistoryFirstPageSchema>;\n\nexport const GameStatHistoryContinuationPageSchema = z.strictObject({\n events: pageEventsSchema.refine(\n (events) => events.length > 0,\n 'A continuation page holds events.',\n ),\n});\nexport type GameStatHistoryContinuationPage = z.infer<typeof GameStatHistoryContinuationPageSchema>;\n\n// Both page shapes are strict, so a first page missing part of its snapshot is refused rather than\n// read as a continuation page.\nexport const GameStatHistoryPageSchema = z.union([\n GameStatHistoryFirstPageSchema,\n GameStatHistoryContinuationPageSchema,\n]);\nexport type GameStatHistoryPage = z.infer<typeof GameStatHistoryPageSchema>;\n"]}
1
+ {"version":3,"file":"game-stat-history.js","sourceRoot":"","sources":["../../src/doc-schemas/game-stat-history.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,qFAAqF;AACrF,6EAA6E;AAC7E,EAAE;AACF,iGAAiG;AACjG,4FAA4F;AAC5F,uGAAuG;AAEvG,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EAAE,wBAAwB,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACtF,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AAEtD,MAAM,gBAAgB,GAAG,CAAC;KACvB,KAAK,CAAC,mBAAmB,CAAC;KAC1B,GAAG,CAAC,sBAAsB,CAAC;KAC3B,MAAM,CACL,CAAC,MAAM,EAAE,EAAE,CACT,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,CAAC,IAAI,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC,CAAC,QAAQ,GAAG,KAAK,CAAC,QAAQ,CAAC,EAC5F,4CAA4C,CAC7C,CAAC;AAEJ,yEAAyE;AACzE,MAAM,CAAC,MAAM,8BAA8B,GAAG,CAAC,CAAC,YAAY,CAAC;IAC3D,MAAM,EAAE,gBAAgB;IACxB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,EAAE,CAAC,QAAQ,EAAE;IACtC,UAAU,EAAE,gBAAgB;IAC5B,OAAO,EAAE,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC5C,mDAAmD;IACnD,eAAe,EAAE,CAAC,CAAC,KAAK,CAAC,wBAAwB,CAAC;IAClD,qBAAqB,EAAE,CAAC,CAAC,KAAK,CAAC,wBAAwB,CAAC;IACxD,yBAAyB,EAAE,CAAC,CAAC,KAAK,CAAC,wBAAwB,CAAC;CAC7D,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,qCAAqC,GAAG,CAAC,CAAC,YAAY,CAAC;IAClE,MAAM,EAAE,gBAAgB,CAAC,MAAM,CAC7B,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,EAC7B,mCAAmC,CACpC;CACF,CAAC,CAAC;AAGH,mGAAmG;AACnG,+BAA+B;AAC/B,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,CAAC,KAAK,CAAC;IAC/C,8BAA8B;IAC9B,qCAAqC;CACtC,CAAC,CAAC","sourcesContent":["// `sportGames/{gameId}/gameStatHistory/{pageId}` — a completed game's finalized record: every event\n// document of the game, exactly as stored and in `sequence` order, in pages of up to\n// STAT_HISTORY_PAGE_SIZE, the page ids from `buildStatHistoryPageId(index)`.\n//\n// The first page also carries what finalize applied: the sport, the teams, and the participation\n// snapshot, so the record alone reproduces a completed game's contribution without the game\n// document a hand edit can reach (RULE-QS-SPORT-924). It exists exactly while the game is `completed`.\n\nimport { z } from 'zod';\nimport { STAT_HISTORY_PAGE_SIZE } from '../constants/sports.js';\nimport { gameRosterPlayerIdSchema, leagueTypeSchema, teamIdSchema } from './atoms.js';\nimport { GameStatEventSchema } from './game-stats.js';\n\nconst pageEventsSchema = z\n .array(GameStatEventSchema)\n .max(STAT_HISTORY_PAGE_SIZE)\n .refine(\n (events) =>\n events.every((event, index) => index === 0 || events[index - 1].sequence < event.sequence),\n 'A page holds its events in sequence order.',\n );\n\n// A game whose replay is its empty stats has a first page and no events.\nexport const GameStatHistoryFirstPageSchema = z.strictObject({\n events: pageEventsSchema,\n pageCount: z.number().int().positive(),\n leagueType: leagueTypeSchema,\n teamIds: z.array(teamIdSchema).min(1).max(2),\n // The participation snapshot, as roster-entry ids.\n rosterPlayerIds: z.array(gameRosterPlayerIdSchema),\n absentRosterPlayerIds: z.array(gameRosterPlayerIdSchema),\n nonScoringRosterPlayerIds: z.array(gameRosterPlayerIdSchema),\n});\nexport type GameStatHistoryFirstPage = z.infer<typeof GameStatHistoryFirstPageSchema>;\n\nexport const GameStatHistoryContinuationPageSchema = z.strictObject({\n events: pageEventsSchema.refine(\n (events) => events.length > 0,\n 'A continuation page holds events.',\n ),\n});\nexport type GameStatHistoryContinuationPage = z.infer<typeof GameStatHistoryContinuationPageSchema>;\n\n// Both page shapes are strict, so a first page missing part of its snapshot is refused rather than\n// read as a continuation page.\nexport const GameStatHistoryPageSchema = z.union([\n GameStatHistoryFirstPageSchema,\n GameStatHistoryContinuationPageSchema,\n]);\nexport type GameStatHistoryPage = z.infer<typeof GameStatHistoryPageSchema>;\n"]}
@@ -1,12 +1,12 @@
1
1
  // The game tier of the stats model: the stat event documents, and the rollup the game document
2
- // carries (SPORT-001, SPORT-101, SPORT-102).
2
+ // carries (RULE-QS-SPORT-901, RULE-QS-SPORT-911, RULE-QS-SPORT-912).
3
3
  //
4
4
  // Each stat event is its own document, `sportGames/{gameId}/gameStatEvents/{eventId}`, and the
5
5
  // event documents are the source of truth: at every commit the game's `stats` equals their replay,
6
6
  // in `sequence` order, from the sport's empty stats. The store is append-only — an undo appends a
7
7
  // `STAT_VOID` and a correction a `STAT_CORRECTION`; no event is ever edited.
8
8
  //
9
- // An event names people by id; names resolve from `publicUsers` at read time (ARCH-103, SPORT-104).
9
+ // An event names people by id; names resolve from `publicUsers` at read time (RULE-STD-ARCH-103, RULE-QS-SPORT-914).
10
10
  // `displayNameSnapshot` and `assistedByDisplayNameSnapshot` are carried only by copied events, as
11
11
  // the audit record of the name the stat was entered under — never read as identity.
12
12
  import { z } from 'zod';
@@ -20,7 +20,7 @@ import { basketballBulkStatKeySchema, countSchema, epochMsSchema, gameRosterPlay
20
20
  // ---------------------------------------------------------------------------------------------
21
21
  // Lines — one per team of the game, one per roster entry holding a non-zero player line
22
22
  // ---------------------------------------------------------------------------------------------
23
- // A line's counters: one field per key of the sport's declared stat-key set (SPORT-007), so a key
23
+ // A line's counters: one field per key of the sport's declared stat-key set (RULE-QS-SPORT-907), so a key
24
24
  // added to the set is a field of every line built from it.
25
25
  function counterShape(keys) {
26
26
  return Object.fromEntries(keys.map((key) => [key, countSchema]));
@@ -44,7 +44,7 @@ export const CurlingTeamStatsSchema = z.strictObject({
44
44
  });
45
45
  export const FootballTeamStatsSchema = z.strictObject(counterShape(FOOTBALL_TEAM_STAT_KEYS));
46
46
  // ---------------------------------------------------------------------------------------------
47
- // The payload — exactly two keys for every sport (SPORT-001)
47
+ // The payload — exactly two keys for every sport (RULE-QS-SPORT-901)
48
48
  // ---------------------------------------------------------------------------------------------
49
49
  // The one declaration of the game `stats` payload. Each sport's is built through it from that
50
50
  // sport's game lines in `SPORT_LINE_SCHEMAS`, as `SPORT_GAME_STATS_SCHEMAS`. `playerStats` is keyed
@@ -60,7 +60,7 @@ export function gameStatsSchema(teamLine, playerLine) {
60
60
  // Events
61
61
  // ---------------------------------------------------------------------------------------------
62
62
  // Every event: its id, its place in the replay, and when it was entered. The recorder facts are
63
- // derived by the writing core, never taken from the payload (ARCH-106); a copied event has none.
63
+ // derived by the writing core, never taken from the payload (RULE-STD-ARCH-106); a copied event has none.
64
64
  const statEventBase = {
65
65
  eventId: statEventIdSchema,
66
66
  sequence: z.number().int().positive(),
@@ -190,7 +190,7 @@ function recorderFactsAgree(event) {
190
190
  }
191
191
  // A name snapshot is the audit record a copied event keeps of the name its stat was entered under.
192
192
  // Only the writing core stamps recorder facts, and it writes ids only, so an event carrying both a
193
- // snapshot and a recorder fact is refused (ARCH-103, SPORT-104).
193
+ // snapshot and a recorder fact is refused (RULE-STD-ARCH-103, RULE-QS-SPORT-914).
194
194
  function snapshotsOnlyOnCopiedEvents(event) {
195
195
  const snapshot = event.displayNameSnapshot !== undefined || event.assistedByDisplayNameSnapshot !== undefined;
196
196
  const recorded = [
@@ -1 +1 @@
1
- {"version":3,"file":"game-stats.js","sourceRoot":"","sources":["../../src/doc-schemas/game-stats.ts"],"names":[],"mappings":"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","sourcesContent":["// The game tier of the stats model: the stat event documents, and the rollup the game document\n// carries (SPORT-001, SPORT-101, SPORT-102).\n//\n// Each stat event is its own document, `sportGames/{gameId}/gameStatEvents/{eventId}`, and the\n// event documents are the source of truth: at every commit the game's `stats` equals their replay,\n// in `sequence` order, from the sport's empty stats. The store is append-only — an undo appends a\n// `STAT_VOID` and a correction a `STAT_CORRECTION`; no event is ever edited.\n//\n// An event names people by id; names resolve from `publicUsers` at read time (ARCH-103, SPORT-104).\n// `displayNameSnapshot` and `assistedByDisplayNameSnapshot` are carried only by copied events, as\n// the audit record of the name the stat was entered under — never read as identity.\n\nimport { z } from 'zod';\nimport { CURLING_ROUND_COUNT, MAX_CURLING_ROUND_POINTS } from '../constants/sports.js';\nimport { buildGameRosterPlayerId } from '../ids/compound-ids.js';\nimport { buildStatCorrectionId, buildStatVoidId } from '../ids/stat-event-ids.js';\nimport {\n BASKETBALL_ASSISTABLE_SHOT_EVENT_TYPE_KEYS,\n BASKETBALL_BULK_EDIT_EVENT_TYPE,\n BASKETBALL_ONE_POINT_EVENT_TYPE,\n BASKETBALL_PLAYER_STAT_KEYS,\n BASKETBALL_PLAYER_TALLY_EVENT_TYPE_KEYS,\n BASKETBALL_TEAM_STAT_KEYS,\n BASKETBALL_TIMEOUT_EVENT_TYPE,\n CURLING_ROUND_BLANK_EVENT_TYPE,\n CURLING_ROUND_SCORE_EVENT_TYPE,\n CURLING_TEAM_STAT_KEYS,\n FOOTBALL_BULK_EDIT_EVENT_TYPE,\n FOOTBALL_SCORE_EVENT_TYPE_KEYS,\n FOOTBALL_TEAM_STAT_KEYS,\n} from '../types/sports.js';\nimport type {\n BasketballStatEventType,\n CurlingStatEventType,\n FootballStatEventType,\n StatCounterKey,\n} from '../types/sports.js';\nimport {\n STAT_CORRECTION_EVENT_TYPE,\n STAT_CORRECTION_SOURCE_KEYS,\n STAT_EVENT_AUTHORITY_KEYS,\n STAT_VOID_EVENT_TYPE,\n} from '../types/stats.js';\nimport { CAPTAIN_TEAM_ROLES } from '../types/unions.js';\nimport {\n basketballBulkStatKeySchema,\n countSchema,\n epochMsSchema,\n gameRosterPlayerIdSchema,\n repairReasonSchema,\n statCounterKeySchema,\n statEventIdSchema,\n statsOperationIdSchema,\n teamIdSchema,\n userIdSchema,\n} from './atoms.js';\n\n// ---------------------------------------------------------------------------------------------\n// Lines — one per team of the game, one per roster entry holding a non-zero player line\n// ---------------------------------------------------------------------------------------------\n\n// A line's counters: one field per key of the sport's declared stat-key set (SPORT-007), so a key\n// added to the set is a field of every line built from it.\nfunction counterShape<K extends StatCounterKey>(keys: readonly K[]): Record<K, typeof countSchema> {\n return Object.fromEntries(keys.map((key) => [key, countSchema])) as Record<K, typeof countSchema>;\n}\n\nexport const BasketballTeamStatsSchema = z.strictObject(counterShape(BASKETBALL_TEAM_STAT_KEYS));\nexport type BasketballTeamStats = z.infer<typeof BasketballTeamStatsSchema>;\n\n// `fantasyPoints` is `basketballFantasyPoints` of the line's own counters, on every basketball\n// player line — this game line and the totals lines built from it — stored so every table and\n// aggregate reads it rather than deriving it. FP is linear in the counters, so a totals line's FP\n// equals the sum of its games' FP.\nexport const BasketballPlayerStatsSchema = z.strictObject({\n ...counterShape(BASKETBALL_PLAYER_STAT_KEYS),\n fantasyPoints: z.number().nonnegative(),\n});\nexport type BasketballPlayerStats = z.infer<typeof BasketballPlayerStatsSchema>;\n\n// `roundScores[n]` is end n + 1: `null` until the end is recorded, `0` on both teams for a blank end.\n// `score` is the sum of the recorded ends.\nexport const CurlingTeamStatsSchema = z.strictObject({\n ...counterShape(CURLING_TEAM_STAT_KEYS),\n roundScores: z\n .array(z.number().int().min(0).max(MAX_CURLING_ROUND_POINTS).nullable())\n .length(CURLING_ROUND_COUNT),\n});\nexport type CurlingTeamStats = z.infer<typeof CurlingTeamStatsSchema>;\n\nexport const FootballTeamStatsSchema = z.strictObject(counterShape(FOOTBALL_TEAM_STAT_KEYS));\nexport type FootballTeamStats = z.infer<typeof FootballTeamStatsSchema>;\n\n// ---------------------------------------------------------------------------------------------\n// The payload — exactly two keys for every sport (SPORT-001)\n// ---------------------------------------------------------------------------------------------\n\n// The one declaration of the game `stats` payload. Each sport's is built through it from that\n// sport's game lines in `SPORT_LINE_SCHEMAS`, as `SPORT_GAME_STATS_SCHEMAS`. `playerStats` is keyed\n// by roster entry, never by person, so a person on both teams of a game holds a line on each side. A\n// sport with no player lines passes `z.never()`, so its map stays empty.\nexport function gameStatsSchema<TeamLine extends z.ZodType, PlayerLine extends z.ZodType>(\n teamLine: TeamLine,\n playerLine: PlayerLine,\n) {\n return z.strictObject({\n teamStats: z.record(teamIdSchema, teamLine),\n playerStats: z.record(gameRosterPlayerIdSchema, playerLine),\n });\n}\n\n// ---------------------------------------------------------------------------------------------\n// Events\n// ---------------------------------------------------------------------------------------------\n\n// Every event: its id, its place in the replay, and when it was entered. The recorder facts are\n// derived by the writing core, never taken from the payload (ARCH-106); a copied event has none.\nconst statEventBase = {\n eventId: statEventIdSchema,\n sequence: z.number().int().positive(),\n recordedAt: epochMsSchema,\n acceptedAt: epochMsSchema.optional(),\n recordedByUserId: userIdSchema.optional(),\n recordedByAuthority: z.enum(STAT_EVENT_AUTHORITY_KEYS).optional(),\n // Only for the two team roles: the team whose authority the event was recorded under.\n recordedByTeamId: teamIdSchema.optional(),\n};\n\n// A player-attributed event names the roster entry `{teamId}_{playerUserId}` whose line it changes.\nconst playerEventFields = {\n teamId: teamIdSchema,\n playerUserId: userIdSchema,\n displayNameSnapshot: z.string().optional(),\n};\n\n// Basketball\n\nexport const BasketballOnePointEventSchema = z.strictObject({\n ...statEventBase,\n ...playerEventFields,\n type: z.literal(BASKETBALL_ONE_POINT_EVENT_TYPE),\n});\n\n// An assisted shot names its assister on the same event, which credits both players.\nexport const BasketballAssistableShotEventSchema = z\n .strictObject({\n ...statEventBase,\n ...playerEventFields,\n type: z.enum(BASKETBALL_ASSISTABLE_SHOT_EVENT_TYPE_KEYS),\n assistedByUserId: userIdSchema.optional(),\n assistedByDisplayNameSnapshot: z.string().optional(),\n })\n .refine(\n (event) =>\n event.assistedByDisplayNameSnapshot === undefined || event.assistedByUserId !== undefined,\n 'An assister snapshot needs the assister.',\n );\n\nexport const BasketballPlayerTallyEventSchema = z.strictObject({\n ...statEventBase,\n ...playerEventFields,\n type: z.enum(BASKETBALL_PLAYER_TALLY_EVENT_TYPE_KEYS),\n});\n\nexport const BasketballTimeoutEventSchema = z.strictObject({\n ...statEventBase,\n teamId: teamIdSchema,\n type: z.literal(BASKETBALL_TIMEOUT_EVENT_TYPE),\n});\n\n// A scorer's edit: `value` is the signed change to `stat`, entered against `previousValue`, the\n// value the form showed. A copied bulk edit has no `previousValue`; nothing validates one again.\nexport const BasketballBulkEditEventSchema = z.strictObject({\n ...statEventBase,\n ...playerEventFields,\n type: z.literal(BASKETBALL_BULK_EDIT_EVENT_TYPE),\n stat: basketballBulkStatKeySchema,\n value: z.number().int(),\n previousValue: countSchema.optional(),\n});\n\n// Curling — an event names the end it records; it names no player.\n\nconst curlingRoundNumberSchema = z.number().int().min(1).max(CURLING_ROUND_COUNT);\n\nexport const CurlingRoundScoreEventSchema = z.strictObject({\n ...statEventBase,\n type: z.literal(CURLING_ROUND_SCORE_EVENT_TYPE),\n teamId: teamIdSchema,\n roundNumber: curlingRoundNumberSchema,\n points: z.number().int().min(1).max(MAX_CURLING_ROUND_POINTS),\n});\n\nexport const CurlingBlankEndEventSchema = z.strictObject({\n ...statEventBase,\n type: z.literal(CURLING_ROUND_BLANK_EVENT_TYPE),\n roundNumber: curlingRoundNumberSchema,\n});\n\n// Football — team-attributed only.\n\nexport const FootballScoreEventSchema = z.strictObject({\n ...statEventBase,\n type: z.enum(FOOTBALL_SCORE_EVENT_TYPE_KEYS),\n teamId: teamIdSchema,\n});\n\nexport const FootballBulkEditEventSchema = z.strictObject({\n ...statEventBase,\n type: z.literal(FOOTBALL_BULK_EDIT_EVENT_TYPE),\n teamId: teamIdSchema,\n value: z.number().int(),\n previousValue: countSchema,\n});\n\n// The store-level events, members of every sport's union.\n\n// Undoes exactly the event it targets. Its id is derived from the target, so a target has at most\n// one void and a second undo of the same event is the same document.\nexport const StatVoidEventSchema = z\n .strictObject({\n ...statEventBase,\n type: z.literal(STAT_VOID_EVENT_TYPE),\n targetEventId: statEventIdSchema,\n })\n .refine((event) => event.eventId === buildStatVoidId(event.targetEventId), {\n message: 'A void is stored at the id its target derives.',\n path: ['eventId'],\n });\n\n// Adds `value` to one correctable field of one line — the player line `{teamId}_{playerUserId}`\n// when it names a player, the team line of `teamId` when it does not. It never cascades: a player\n// correction does not move the team's score.\nexport const StatCorrectionEventSchema = z\n .strictObject({\n ...statEventBase,\n type: z.literal(STAT_CORRECTION_EVENT_TYPE),\n teamId: teamIdSchema,\n playerUserId: userIdSchema.optional(),\n stat: statCounterKeySchema,\n value: z.number().int(),\n correctionSource: z.enum(STAT_CORRECTION_SOURCE_KEYS),\n operationId: statsOperationIdSchema,\n reason: repairReasonSchema,\n // The ghost a link re-keyed this correction's player line from. The id was derived from the\n // ghost's line when the correction was written, and a document id never changes.\n rekeyedFromGhostUserId: userIdSchema.optional(),\n })\n .refine(\n (event) =>\n event.eventId ===\n buildStatCorrectionId(\n event.operationId,\n event.playerUserId === undefined\n ? event.teamId\n : buildGameRosterPlayerId(\n event.teamId,\n event.rekeyedFromGhostUserId ?? event.playerUserId,\n ),\n event.stat,\n ),\n {\n message: 'A correction is stored at the id its operation, line, and field derive.',\n path: ['eventId'],\n },\n );\n\n// Recorder facts are all present or all absent, and a team is named exactly when the authority is a\n// team role.\nfunction recorderFactsAgree(event: {\n acceptedAt?: number;\n recordedByUserId?: string;\n recordedByAuthority?: (typeof STAT_EVENT_AUTHORITY_KEYS)[number];\n recordedByTeamId?: string;\n}): boolean {\n const facts = [event.acceptedAt, event.recordedByUserId, event.recordedByAuthority];\n const present = facts.filter((fact) => fact !== undefined).length;\n if (present !== 0 && present !== facts.length) return false;\n const teamAuthority = (CAPTAIN_TEAM_ROLES as readonly string[]).includes(\n event.recordedByAuthority ?? '',\n );\n return teamAuthority === (event.recordedByTeamId !== undefined);\n}\n\n// A name snapshot is the audit record a copied event keeps of the name its stat was entered under.\n// Only the writing core stamps recorder facts, and it writes ids only, so an event carrying both a\n// snapshot and a recorder fact is refused (ARCH-103, SPORT-104).\nfunction snapshotsOnlyOnCopiedEvents(event: {\n acceptedAt?: number;\n recordedByUserId?: string;\n recordedByAuthority?: string;\n recordedByTeamId?: string;\n displayNameSnapshot?: string;\n assistedByDisplayNameSnapshot?: string;\n}): boolean {\n const snapshot =\n event.displayNameSnapshot !== undefined || event.assistedByDisplayNameSnapshot !== undefined;\n const recorded = [\n event.acceptedAt,\n event.recordedByUserId,\n event.recordedByAuthority,\n event.recordedByTeamId,\n ].some((fact) => fact !== undefined);\n return !(snapshot && recorded);\n}\n\n// Event types are disjoint across sports, so `type` alone discriminates the whole union.\nexport const GameStatEventSchema = z\n .discriminatedUnion('type', [\n BasketballOnePointEventSchema,\n BasketballAssistableShotEventSchema,\n BasketballPlayerTallyEventSchema,\n BasketballTimeoutEventSchema,\n BasketballBulkEditEventSchema,\n CurlingRoundScoreEventSchema,\n CurlingBlankEndEventSchema,\n FootballScoreEventSchema,\n FootballBulkEditEventSchema,\n StatVoidEventSchema,\n StatCorrectionEventSchema,\n ])\n .refine(\n recorderFactsAgree,\n 'Recorder facts are all present, or all absent, with a team only for a team role.',\n )\n .refine(\n snapshotsOnlyOnCopiedEvents,\n 'A name snapshot belongs to a copied event, which carries no recorder facts.',\n );\nexport type GameStatEvent = z.infer<typeof GameStatEventSchema>;\nexport type GameStatEventType = GameStatEvent['type'];\n\nexport type BasketballStatEvent = Extract<GameStatEvent, { type: BasketballStatEventType }>;\nexport type CurlingRoundScoreEvent = z.infer<typeof CurlingRoundScoreEventSchema>;\nexport type CurlingBlankEndEvent = z.infer<typeof CurlingBlankEndEventSchema>;\nexport type CurlingStatEvent = Extract<GameStatEvent, { type: CurlingStatEventType }>;\nexport type FootballStatEvent = Extract<GameStatEvent, { type: FootballStatEventType }>;\nexport type StatVoidEvent = z.infer<typeof StatVoidEventSchema>;\nexport type StatCorrectionEvent = z.infer<typeof StatCorrectionEventSchema>;\n"]}
1
+ {"version":3,"file":"game-stats.js","sourceRoot":"","sources":["../../src/doc-schemas/game-stats.ts"],"names":[],"mappings":"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","sourcesContent":["// The game tier of the stats model: the stat event documents, and the rollup the game document\n// carries (RULE-QS-SPORT-901, RULE-QS-SPORT-911, RULE-QS-SPORT-912).\n//\n// Each stat event is its own document, `sportGames/{gameId}/gameStatEvents/{eventId}`, and the\n// event documents are the source of truth: at every commit the game's `stats` equals their replay,\n// in `sequence` order, from the sport's empty stats. The store is append-only — an undo appends a\n// `STAT_VOID` and a correction a `STAT_CORRECTION`; no event is ever edited.\n//\n// An event names people by id; names resolve from `publicUsers` at read time (RULE-STD-ARCH-103, RULE-QS-SPORT-914).\n// `displayNameSnapshot` and `assistedByDisplayNameSnapshot` are carried only by copied events, as\n// the audit record of the name the stat was entered under — never read as identity.\n\nimport { z } from 'zod';\nimport { CURLING_ROUND_COUNT, MAX_CURLING_ROUND_POINTS } from '../constants/sports.js';\nimport { buildGameRosterPlayerId } from '../ids/compound-ids.js';\nimport { buildStatCorrectionId, buildStatVoidId } from '../ids/stat-event-ids.js';\nimport {\n BASKETBALL_ASSISTABLE_SHOT_EVENT_TYPE_KEYS,\n BASKETBALL_BULK_EDIT_EVENT_TYPE,\n BASKETBALL_ONE_POINT_EVENT_TYPE,\n BASKETBALL_PLAYER_STAT_KEYS,\n BASKETBALL_PLAYER_TALLY_EVENT_TYPE_KEYS,\n BASKETBALL_TEAM_STAT_KEYS,\n BASKETBALL_TIMEOUT_EVENT_TYPE,\n CURLING_ROUND_BLANK_EVENT_TYPE,\n CURLING_ROUND_SCORE_EVENT_TYPE,\n CURLING_TEAM_STAT_KEYS,\n FOOTBALL_BULK_EDIT_EVENT_TYPE,\n FOOTBALL_SCORE_EVENT_TYPE_KEYS,\n FOOTBALL_TEAM_STAT_KEYS,\n} from '../types/sports.js';\nimport type {\n BasketballStatEventType,\n CurlingStatEventType,\n FootballStatEventType,\n StatCounterKey,\n} from '../types/sports.js';\nimport {\n STAT_CORRECTION_EVENT_TYPE,\n STAT_CORRECTION_SOURCE_KEYS,\n STAT_EVENT_AUTHORITY_KEYS,\n STAT_VOID_EVENT_TYPE,\n} from '../types/stats.js';\nimport { CAPTAIN_TEAM_ROLES } from '../types/unions.js';\nimport {\n basketballBulkStatKeySchema,\n countSchema,\n epochMsSchema,\n gameRosterPlayerIdSchema,\n repairReasonSchema,\n statCounterKeySchema,\n statEventIdSchema,\n statsOperationIdSchema,\n teamIdSchema,\n userIdSchema,\n} from './atoms.js';\n\n// ---------------------------------------------------------------------------------------------\n// Lines — one per team of the game, one per roster entry holding a non-zero player line\n// ---------------------------------------------------------------------------------------------\n\n// A line's counters: one field per key of the sport's declared stat-key set (RULE-QS-SPORT-907), so a key\n// added to the set is a field of every line built from it.\nfunction counterShape<K extends StatCounterKey>(keys: readonly K[]): Record<K, typeof countSchema> {\n return Object.fromEntries(keys.map((key) => [key, countSchema])) as Record<K, typeof countSchema>;\n}\n\nexport const BasketballTeamStatsSchema = z.strictObject(counterShape(BASKETBALL_TEAM_STAT_KEYS));\nexport type BasketballTeamStats = z.infer<typeof BasketballTeamStatsSchema>;\n\n// `fantasyPoints` is `basketballFantasyPoints` of the line's own counters, on every basketball\n// player line — this game line and the totals lines built from it — stored so every table and\n// aggregate reads it rather than deriving it. FP is linear in the counters, so a totals line's FP\n// equals the sum of its games' FP.\nexport const BasketballPlayerStatsSchema = z.strictObject({\n ...counterShape(BASKETBALL_PLAYER_STAT_KEYS),\n fantasyPoints: z.number().nonnegative(),\n});\nexport type BasketballPlayerStats = z.infer<typeof BasketballPlayerStatsSchema>;\n\n// `roundScores[n]` is end n + 1: `null` until the end is recorded, `0` on both teams for a blank end.\n// `score` is the sum of the recorded ends.\nexport const CurlingTeamStatsSchema = z.strictObject({\n ...counterShape(CURLING_TEAM_STAT_KEYS),\n roundScores: z\n .array(z.number().int().min(0).max(MAX_CURLING_ROUND_POINTS).nullable())\n .length(CURLING_ROUND_COUNT),\n});\nexport type CurlingTeamStats = z.infer<typeof CurlingTeamStatsSchema>;\n\nexport const FootballTeamStatsSchema = z.strictObject(counterShape(FOOTBALL_TEAM_STAT_KEYS));\nexport type FootballTeamStats = z.infer<typeof FootballTeamStatsSchema>;\n\n// ---------------------------------------------------------------------------------------------\n// The payload — exactly two keys for every sport (RULE-QS-SPORT-901)\n// ---------------------------------------------------------------------------------------------\n\n// The one declaration of the game `stats` payload. Each sport's is built through it from that\n// sport's game lines in `SPORT_LINE_SCHEMAS`, as `SPORT_GAME_STATS_SCHEMAS`. `playerStats` is keyed\n// by roster entry, never by person, so a person on both teams of a game holds a line on each side. A\n// sport with no player lines passes `z.never()`, so its map stays empty.\nexport function gameStatsSchema<TeamLine extends z.ZodType, PlayerLine extends z.ZodType>(\n teamLine: TeamLine,\n playerLine: PlayerLine,\n) {\n return z.strictObject({\n teamStats: z.record(teamIdSchema, teamLine),\n playerStats: z.record(gameRosterPlayerIdSchema, playerLine),\n });\n}\n\n// ---------------------------------------------------------------------------------------------\n// Events\n// ---------------------------------------------------------------------------------------------\n\n// Every event: its id, its place in the replay, and when it was entered. The recorder facts are\n// derived by the writing core, never taken from the payload (RULE-STD-ARCH-106); a copied event has none.\nconst statEventBase = {\n eventId: statEventIdSchema,\n sequence: z.number().int().positive(),\n recordedAt: epochMsSchema,\n acceptedAt: epochMsSchema.optional(),\n recordedByUserId: userIdSchema.optional(),\n recordedByAuthority: z.enum(STAT_EVENT_AUTHORITY_KEYS).optional(),\n // Only for the two team roles: the team whose authority the event was recorded under.\n recordedByTeamId: teamIdSchema.optional(),\n};\n\n// A player-attributed event names the roster entry `{teamId}_{playerUserId}` whose line it changes.\nconst playerEventFields = {\n teamId: teamIdSchema,\n playerUserId: userIdSchema,\n displayNameSnapshot: z.string().optional(),\n};\n\n// Basketball\n\nexport const BasketballOnePointEventSchema = z.strictObject({\n ...statEventBase,\n ...playerEventFields,\n type: z.literal(BASKETBALL_ONE_POINT_EVENT_TYPE),\n});\n\n// An assisted shot names its assister on the same event, which credits both players.\nexport const BasketballAssistableShotEventSchema = z\n .strictObject({\n ...statEventBase,\n ...playerEventFields,\n type: z.enum(BASKETBALL_ASSISTABLE_SHOT_EVENT_TYPE_KEYS),\n assistedByUserId: userIdSchema.optional(),\n assistedByDisplayNameSnapshot: z.string().optional(),\n })\n .refine(\n (event) =>\n event.assistedByDisplayNameSnapshot === undefined || event.assistedByUserId !== undefined,\n 'An assister snapshot needs the assister.',\n );\n\nexport const BasketballPlayerTallyEventSchema = z.strictObject({\n ...statEventBase,\n ...playerEventFields,\n type: z.enum(BASKETBALL_PLAYER_TALLY_EVENT_TYPE_KEYS),\n});\n\nexport const BasketballTimeoutEventSchema = z.strictObject({\n ...statEventBase,\n teamId: teamIdSchema,\n type: z.literal(BASKETBALL_TIMEOUT_EVENT_TYPE),\n});\n\n// A scorer's edit: `value` is the signed change to `stat`, entered against `previousValue`, the\n// value the form showed. A copied bulk edit has no `previousValue`; nothing validates one again.\nexport const BasketballBulkEditEventSchema = z.strictObject({\n ...statEventBase,\n ...playerEventFields,\n type: z.literal(BASKETBALL_BULK_EDIT_EVENT_TYPE),\n stat: basketballBulkStatKeySchema,\n value: z.number().int(),\n previousValue: countSchema.optional(),\n});\n\n// Curling — an event names the end it records; it names no player.\n\nconst curlingRoundNumberSchema = z.number().int().min(1).max(CURLING_ROUND_COUNT);\n\nexport const CurlingRoundScoreEventSchema = z.strictObject({\n ...statEventBase,\n type: z.literal(CURLING_ROUND_SCORE_EVENT_TYPE),\n teamId: teamIdSchema,\n roundNumber: curlingRoundNumberSchema,\n points: z.number().int().min(1).max(MAX_CURLING_ROUND_POINTS),\n});\n\nexport const CurlingBlankEndEventSchema = z.strictObject({\n ...statEventBase,\n type: z.literal(CURLING_ROUND_BLANK_EVENT_TYPE),\n roundNumber: curlingRoundNumberSchema,\n});\n\n// Football — team-attributed only.\n\nexport const FootballScoreEventSchema = z.strictObject({\n ...statEventBase,\n type: z.enum(FOOTBALL_SCORE_EVENT_TYPE_KEYS),\n teamId: teamIdSchema,\n});\n\nexport const FootballBulkEditEventSchema = z.strictObject({\n ...statEventBase,\n type: z.literal(FOOTBALL_BULK_EDIT_EVENT_TYPE),\n teamId: teamIdSchema,\n value: z.number().int(),\n previousValue: countSchema,\n});\n\n// The store-level events, members of every sport's union.\n\n// Undoes exactly the event it targets. Its id is derived from the target, so a target has at most\n// one void and a second undo of the same event is the same document.\nexport const StatVoidEventSchema = z\n .strictObject({\n ...statEventBase,\n type: z.literal(STAT_VOID_EVENT_TYPE),\n targetEventId: statEventIdSchema,\n })\n .refine((event) => event.eventId === buildStatVoidId(event.targetEventId), {\n message: 'A void is stored at the id its target derives.',\n path: ['eventId'],\n });\n\n// Adds `value` to one correctable field of one line — the player line `{teamId}_{playerUserId}`\n// when it names a player, the team line of `teamId` when it does not. It never cascades: a player\n// correction does not move the team's score.\nexport const StatCorrectionEventSchema = z\n .strictObject({\n ...statEventBase,\n type: z.literal(STAT_CORRECTION_EVENT_TYPE),\n teamId: teamIdSchema,\n playerUserId: userIdSchema.optional(),\n stat: statCounterKeySchema,\n value: z.number().int(),\n correctionSource: z.enum(STAT_CORRECTION_SOURCE_KEYS),\n operationId: statsOperationIdSchema,\n reason: repairReasonSchema,\n // The ghost a link re-keyed this correction's player line from. The id was derived from the\n // ghost's line when the correction was written, and a document id never changes.\n rekeyedFromGhostUserId: userIdSchema.optional(),\n })\n .refine(\n (event) =>\n event.eventId ===\n buildStatCorrectionId(\n event.operationId,\n event.playerUserId === undefined\n ? event.teamId\n : buildGameRosterPlayerId(\n event.teamId,\n event.rekeyedFromGhostUserId ?? event.playerUserId,\n ),\n event.stat,\n ),\n {\n message: 'A correction is stored at the id its operation, line, and field derive.',\n path: ['eventId'],\n },\n );\n\n// Recorder facts are all present or all absent, and a team is named exactly when the authority is a\n// team role.\nfunction recorderFactsAgree(event: {\n acceptedAt?: number;\n recordedByUserId?: string;\n recordedByAuthority?: (typeof STAT_EVENT_AUTHORITY_KEYS)[number];\n recordedByTeamId?: string;\n}): boolean {\n const facts = [event.acceptedAt, event.recordedByUserId, event.recordedByAuthority];\n const present = facts.filter((fact) => fact !== undefined).length;\n if (present !== 0 && present !== facts.length) return false;\n const teamAuthority = (CAPTAIN_TEAM_ROLES as readonly string[]).includes(\n event.recordedByAuthority ?? '',\n );\n return teamAuthority === (event.recordedByTeamId !== undefined);\n}\n\n// A name snapshot is the audit record a copied event keeps of the name its stat was entered under.\n// Only the writing core stamps recorder facts, and it writes ids only, so an event carrying both a\n// snapshot and a recorder fact is refused (RULE-STD-ARCH-103, RULE-QS-SPORT-914).\nfunction snapshotsOnlyOnCopiedEvents(event: {\n acceptedAt?: number;\n recordedByUserId?: string;\n recordedByAuthority?: string;\n recordedByTeamId?: string;\n displayNameSnapshot?: string;\n assistedByDisplayNameSnapshot?: string;\n}): boolean {\n const snapshot =\n event.displayNameSnapshot !== undefined || event.assistedByDisplayNameSnapshot !== undefined;\n const recorded = [\n event.acceptedAt,\n event.recordedByUserId,\n event.recordedByAuthority,\n event.recordedByTeamId,\n ].some((fact) => fact !== undefined);\n return !(snapshot && recorded);\n}\n\n// Event types are disjoint across sports, so `type` alone discriminates the whole union.\nexport const GameStatEventSchema = z\n .discriminatedUnion('type', [\n BasketballOnePointEventSchema,\n BasketballAssistableShotEventSchema,\n BasketballPlayerTallyEventSchema,\n BasketballTimeoutEventSchema,\n BasketballBulkEditEventSchema,\n CurlingRoundScoreEventSchema,\n CurlingBlankEndEventSchema,\n FootballScoreEventSchema,\n FootballBulkEditEventSchema,\n StatVoidEventSchema,\n StatCorrectionEventSchema,\n ])\n .refine(\n recorderFactsAgree,\n 'Recorder facts are all present, or all absent, with a team only for a team role.',\n )\n .refine(\n snapshotsOnlyOnCopiedEvents,\n 'A name snapshot belongs to a copied event, which carries no recorder facts.',\n );\nexport type GameStatEvent = z.infer<typeof GameStatEventSchema>;\nexport type GameStatEventType = GameStatEvent['type'];\n\nexport type BasketballStatEvent = Extract<GameStatEvent, { type: BasketballStatEventType }>;\nexport type CurlingRoundScoreEvent = z.infer<typeof CurlingRoundScoreEventSchema>;\nexport type CurlingBlankEndEvent = z.infer<typeof CurlingBlankEndEventSchema>;\nexport type CurlingStatEvent = Extract<GameStatEvent, { type: CurlingStatEventType }>;\nexport type FootballStatEvent = Extract<GameStatEvent, { type: FootballStatEventType }>;\nexport type StatVoidEvent = z.infer<typeof StatVoidEventSchema>;\nexport type StatCorrectionEvent = z.infer<typeof StatCorrectionEventSchema>;\n"]}
@@ -25,26 +25,26 @@ export const LeagueTeamSchema = z.object({
25
25
  // Advanced by every membership change on the team after its creation; absent until the first.
26
26
  // Only its changing matters: game creation and a game's team replacement read this document in the
27
27
  // transaction that seeds a roster from the team's memberships, so a membership change landing at
28
- // the same moment conflicts with them instead of missing the new game (BACKEND-116).
28
+ // the same moment conflicts with them instead of missing the new game (RULE-STD-BACKEND-116).
29
29
  membershipRevision: countSchema.optional(),
30
30
  // Advanced by every invitation and join request a door creates for the team; absent until the
31
31
  // first. Only its changing matters: a pending invitation or request between one team and one
32
32
  // person is unique, and a membership on the team closes both, but a transaction does not lock a
33
33
  // query against an insert — so each creation writes this document, which every competing
34
34
  // creation and every membership door also writes, and two racers conflict instead of both passing
35
- // their checks (BACKEND-116).
35
+ // their checks (RULE-STD-BACKEND-116).
36
36
  joinRevision: countSchema.optional(),
37
37
  // Advanced by every game creation and every game edit that brings the team in; absent until the
38
38
  // first. Only its changing matters: a team's deletion re-reads the team's games in the transaction
39
39
  // that deletes the team document, and a query does not lock against an insert — so a game created
40
40
  // for the team writes this document, which the deletion also writes, and the two conflict instead of
41
- // leaving a game naming a deleted team (BACKEND-116).
41
+ // leaving a game naming a deleted team (RULE-STD-BACKEND-116).
42
42
  scheduleRevision: countSchema.optional(),
43
43
  createdAt: epochMsSchema,
44
44
  updatedAt: epochMsSchema,
45
45
  });
46
46
  // `leagueTeamContacts/{teamId}` — how the league reaches the team. Admin read only: the team
47
- // document is public, so the phone lives here, never on it (BACKEND-105). `leagueId` is what lists a
47
+ // document is public, so the phone lives here, never on it (RULE-STD-BACKEND-105). `leagueId` is what lists a
48
48
  // league's contacts in one query. Written with the team by team creation.
49
49
  export const LeagueTeamContactSchema = z.object({
50
50
  leagueId: leagueIdSchema,
@@ -1 +1 @@
1
- {"version":3,"file":"league-team.js","sourceRoot":"","sources":["../../src/doc-schemas/league-team.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,wEAAwE;AAExE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,0BAA0B,EAAE,MAAM,mCAAmC,CAAC;AAC/E,OAAO,EACL,WAAW,EACX,aAAa,EACb,cAAc,EACd,cAAc,EACd,cAAc,EACd,eAAe,EACf,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,IAAI,EAAE,cAAc;IACpB,QAAQ,EAAE,cAAc;IACxB,qDAAqD;IACrD,QAAQ,EAAE,cAAc,CAAC,QAAQ,EAAE;IACnC,+FAA+F;IAC/F,kEAAkE;IAClE,eAAe,EAAE,YAAY;IAC7B,uEAAuE;IACvE,WAAW,EAAE,qBAAqB,CAAC,QAAQ,EAAE;IAC7C,kGAAkG;IAClG,0FAA0F;IAC1F,iBAAiB,EAAE,CAAC,CAAC,MAAM,CAAC,YAAY,EAAE,uBAAuB,CAAC,CAAC,QAAQ,EAAE;IAC7E,gGAAgG;IAChG,uDAAuD;IACvD,QAAQ,EAAE,oBAAoB,CAAC,QAAQ,EAAE;IACzC,8FAA8F;IAC9F,mGAAmG;IACnG,iGAAiG;IACjG,qFAAqF;IACrF,kBAAkB,EAAE,WAAW,CAAC,QAAQ,EAAE;IAC1C,8FAA8F;IAC9F,6FAA6F;IAC7F,gGAAgG;IAChG,yFAAyF;IACzF,kGAAkG;IAClG,8BAA8B;IAC9B,YAAY,EAAE,WAAW,CAAC,QAAQ,EAAE;IACpC,gGAAgG;IAChG,mGAAmG;IACnG,kGAAkG;IAClG,qGAAqG;IACrG,sDAAsD;IACtD,gBAAgB,EAAE,WAAW,CAAC,QAAQ,EAAE;IACxC,SAAS,EAAE,aAAa;IACxB,SAAS,EAAE,aAAa;CACzB,CAAC,CAAC;AAGH,6FAA6F;AAC7F,qGAAqG;AACrG,0EAA0E;AAC1E,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,QAAQ,EAAE,cAAc;IACxB,6FAA6F;IAC7F,YAAY,EAAE,eAAe,CAAC,0BAA0B,CAAC;IACzD,SAAS,EAAE,aAAa;CACzB,CAAC,CAAC","sourcesContent":["// `leagueTeams/{teamId}` — one team in one league, with its derived season rollups. Who is on the\n// team, and who captains it, is `teamMemberships` — never a field here.\n\nimport { z } from 'zod';\nimport { CONTACT_PHONE_INPUT_FORMAT } from '../input-formats/input-formats.js';\nimport {\n countSchema,\n epochMsSchema,\n leagueIdSchema,\n teamIconSchema,\n teamTypeSchema,\n textFieldSchema,\n userIdSchema,\n} from './atoms.js';\nimport { PlayerSeasonStatsSchema, TeamSeasonStatsSchema } from './aggregate-stats.js';\nimport { GallerySummarySchema } from './gallery.js';\n\nexport const LeagueTeamSchema = z.object({\n name: z.string(),\n icon: teamIconSchema,\n leagueId: leagueIdSchema,\n // Present only in a Competitive/Recreational league.\n teamType: teamTypeSchema.optional(),\n // The account that created the team — the new captain, or the admin who created it for a ghost\n // captain. A creation record, never a membership or an authority.\n createdByUserId: userIdSchema,\n // The team's standings line, absent until its first game is finalized.\n seasonStats: TeamSeasonStatsSchema.optional(),\n // The league player stats: one row per person the team's games credited, keyed by userId (real or\n // ghost). The team's league, the team, and the key are the membership the row belongs to.\n playerSeasonStats: z.record(userIdSchema, PlayerSeasonStatsSchema).optional(),\n // The team's picture gallery summary — absent is an empty gallery. Only the picture core writes\n // it; the team callables and the copy action never do.\n pictures: GallerySummarySchema.optional(),\n // Advanced by every membership change on the team after its creation; absent until the first.\n // Only its changing matters: game creation and a game's team replacement read this document in the\n // transaction that seeds a roster from the team's memberships, so a membership change landing at\n // the same moment conflicts with them instead of missing the new game (BACKEND-116).\n membershipRevision: countSchema.optional(),\n // Advanced by every invitation and join request a door creates for the team; absent until the\n // first. Only its changing matters: a pending invitation or request between one team and one\n // person is unique, and a membership on the team closes both, but a transaction does not lock a\n // query against an insert — so each creation writes this document, which every competing\n // creation and every membership door also writes, and two racers conflict instead of both passing\n // their checks (BACKEND-116).\n joinRevision: countSchema.optional(),\n // Advanced by every game creation and every game edit that brings the team in; absent until the\n // first. Only its changing matters: a team's deletion re-reads the team's games in the transaction\n // that deletes the team document, and a query does not lock against an insert — so a game created\n // for the team writes this document, which the deletion also writes, and the two conflict instead of\n // leaving a game naming a deleted team (BACKEND-116).\n scheduleRevision: countSchema.optional(),\n createdAt: epochMsSchema,\n updatedAt: epochMsSchema,\n});\nexport type LeagueTeam = z.infer<typeof LeagueTeamSchema>;\n\n// `leagueTeamContacts/{teamId}` — how the league reaches the team. Admin read only: the team\n// document is public, so the phone lives here, never on it (BACKEND-105). `leagueId` is what lists a\n// league's contacts in one query. Written with the team by team creation.\nexport const LeagueTeamContactSchema = z.object({\n leagueId: leagueIdSchema,\n // The number as the contact callables kept it, held to the same declaration they parse with.\n contactPhone: textFieldSchema(CONTACT_PHONE_INPUT_FORMAT),\n updatedAt: epochMsSchema,\n});\nexport type LeagueTeamContact = z.infer<typeof LeagueTeamContactSchema>;\n"]}
1
+ {"version":3,"file":"league-team.js","sourceRoot":"","sources":["../../src/doc-schemas/league-team.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,wEAAwE;AAExE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,0BAA0B,EAAE,MAAM,mCAAmC,CAAC;AAC/E,OAAO,EACL,WAAW,EACX,aAAa,EACb,cAAc,EACd,cAAc,EACd,cAAc,EACd,eAAe,EACf,YAAY,GACb,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,uBAAuB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AACtF,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAEpD,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE;IAChB,IAAI,EAAE,cAAc;IACpB,QAAQ,EAAE,cAAc;IACxB,qDAAqD;IACrD,QAAQ,EAAE,cAAc,CAAC,QAAQ,EAAE;IACnC,+FAA+F;IAC/F,kEAAkE;IAClE,eAAe,EAAE,YAAY;IAC7B,uEAAuE;IACvE,WAAW,EAAE,qBAAqB,CAAC,QAAQ,EAAE;IAC7C,kGAAkG;IAClG,0FAA0F;IAC1F,iBAAiB,EAAE,CAAC,CAAC,MAAM,CAAC,YAAY,EAAE,uBAAuB,CAAC,CAAC,QAAQ,EAAE;IAC7E,gGAAgG;IAChG,uDAAuD;IACvD,QAAQ,EAAE,oBAAoB,CAAC,QAAQ,EAAE;IACzC,8FAA8F;IAC9F,mGAAmG;IACnG,iGAAiG;IACjG,8FAA8F;IAC9F,kBAAkB,EAAE,WAAW,CAAC,QAAQ,EAAE;IAC1C,8FAA8F;IAC9F,6FAA6F;IAC7F,gGAAgG;IAChG,yFAAyF;IACzF,kGAAkG;IAClG,uCAAuC;IACvC,YAAY,EAAE,WAAW,CAAC,QAAQ,EAAE;IACpC,gGAAgG;IAChG,mGAAmG;IACnG,kGAAkG;IAClG,qGAAqG;IACrG,+DAA+D;IAC/D,gBAAgB,EAAE,WAAW,CAAC,QAAQ,EAAE;IACxC,SAAS,EAAE,aAAa;IACxB,SAAS,EAAE,aAAa;CACzB,CAAC,CAAC;AAGH,6FAA6F;AAC7F,8GAA8G;AAC9G,0EAA0E;AAC1E,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC,CAAC,MAAM,CAAC;IAC9C,QAAQ,EAAE,cAAc;IACxB,6FAA6F;IAC7F,YAAY,EAAE,eAAe,CAAC,0BAA0B,CAAC;IACzD,SAAS,EAAE,aAAa;CACzB,CAAC,CAAC","sourcesContent":["// `leagueTeams/{teamId}` — one team in one league, with its derived season rollups. Who is on the\n// team, and who captains it, is `teamMemberships` — never a field here.\n\nimport { z } from 'zod';\nimport { CONTACT_PHONE_INPUT_FORMAT } from '../input-formats/input-formats.js';\nimport {\n countSchema,\n epochMsSchema,\n leagueIdSchema,\n teamIconSchema,\n teamTypeSchema,\n textFieldSchema,\n userIdSchema,\n} from './atoms.js';\nimport { PlayerSeasonStatsSchema, TeamSeasonStatsSchema } from './aggregate-stats.js';\nimport { GallerySummarySchema } from './gallery.js';\n\nexport const LeagueTeamSchema = z.object({\n name: z.string(),\n icon: teamIconSchema,\n leagueId: leagueIdSchema,\n // Present only in a Competitive/Recreational league.\n teamType: teamTypeSchema.optional(),\n // The account that created the team — the new captain, or the admin who created it for a ghost\n // captain. A creation record, never a membership or an authority.\n createdByUserId: userIdSchema,\n // The team's standings line, absent until its first game is finalized.\n seasonStats: TeamSeasonStatsSchema.optional(),\n // The league player stats: one row per person the team's games credited, keyed by userId (real or\n // ghost). The team's league, the team, and the key are the membership the row belongs to.\n playerSeasonStats: z.record(userIdSchema, PlayerSeasonStatsSchema).optional(),\n // The team's picture gallery summary — absent is an empty gallery. Only the picture core writes\n // it; the team callables and the copy action never do.\n pictures: GallerySummarySchema.optional(),\n // Advanced by every membership change on the team after its creation; absent until the first.\n // Only its changing matters: game creation and a game's team replacement read this document in the\n // transaction that seeds a roster from the team's memberships, so a membership change landing at\n // the same moment conflicts with them instead of missing the new game (RULE-STD-BACKEND-116).\n membershipRevision: countSchema.optional(),\n // Advanced by every invitation and join request a door creates for the team; absent until the\n // first. Only its changing matters: a pending invitation or request between one team and one\n // person is unique, and a membership on the team closes both, but a transaction does not lock a\n // query against an insert — so each creation writes this document, which every competing\n // creation and every membership door also writes, and two racers conflict instead of both passing\n // their checks (RULE-STD-BACKEND-116).\n joinRevision: countSchema.optional(),\n // Advanced by every game creation and every game edit that brings the team in; absent until the\n // first. Only its changing matters: a team's deletion re-reads the team's games in the transaction\n // that deletes the team document, and a query does not lock against an insert — so a game created\n // for the team writes this document, which the deletion also writes, and the two conflict instead of\n // leaving a game naming a deleted team (RULE-STD-BACKEND-116).\n scheduleRevision: countSchema.optional(),\n createdAt: epochMsSchema,\n updatedAt: epochMsSchema,\n});\nexport type LeagueTeam = z.infer<typeof LeagueTeamSchema>;\n\n// `leagueTeamContacts/{teamId}` — how the league reaches the team. Admin read only: the team\n// document is public, so the phone lives here, never on it (RULE-STD-BACKEND-105). `leagueId` is what lists a\n// league's contacts in one query. Written with the team by team creation.\nexport const LeagueTeamContactSchema = z.object({\n leagueId: leagueIdSchema,\n // The number as the contact callables kept it, held to the same declaration they parse with.\n contactPhone: textFieldSchema(CONTACT_PHONE_INPUT_FORMAT),\n updatedAt: epochMsSchema,\n});\nexport type LeagueTeamContact = z.infer<typeof LeagueTeamContactSchema>;\n"]}
@@ -1,11 +1,11 @@
1
1
  // `membershipRosterSyncs/{teamMembershipId}` — one team membership's durable obligation to bring
2
- // its team's open game rosters in line with it (QUALITY-104, BACKEND-302).
2
+ // its team's open game rosters in line with it (RULE-STD-QUALITY-104, RULE-STD-BACKEND-302).
3
3
  //
4
4
  // Every change to a membership — made, removed, or linked — advances `rosterRevision` in the same
5
5
  // transaction as the change, so a committed membership can never lose its roster work. The roster
6
6
  // core raises `reconciledRosterRevision` to the revision it finished; the obligation is outstanding
7
7
  // while it is lower. The document outlives a deleted membership, so a removal is still reconciled,
8
- // and it is keyed by the membership id, so no second id format exists (ARCH-109). Backend-only.
8
+ // and it is keyed by the membership id, so no second id format exists (RULE-STD-ARCH-109). Backend-only.
9
9
  import { z } from 'zod';
10
10
  import { MAX_ROSTER_SYNC_FAILURE_MESSAGE_LENGTH } from '../constants/business.js';
11
11
  import { FAILED_ROSTER_SYNC_STATUS, RECONCILED_ROSTER_SYNC_STATUS } from '../types/unions.js';
@@ -1 +1 @@
1
- {"version":3,"file":"membership-roster-sync.js","sourceRoot":"","sources":["../../src/doc-schemas/membership-roster-sync.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,2EAA2E;AAC3E,EAAE;AACF,kGAAkG;AAClG,kGAAkG;AAClG,oGAAoG;AACpG,mGAAmG;AACnG,gGAAgG;AAEhG,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,sCAAsC,EAAE,MAAM,0BAA0B,CAAC;AAClF,OAAO,EAAE,yBAAyB,EAAE,6BAA6B,EAAE,MAAM,oBAAoB,CAAC;AAC9F,OAAO,EACL,qBAAqB,EACrB,WAAW,EACX,aAAa,EACb,YAAY,EACZ,cAAc,EACd,gCAAgC,EAChC,cAAc,EACd,YAAY,EACZ,YAAY,GACb,MAAM,YAAY,CAAC;AAEpB,mGAAmG;AACnG,0FAA0F;AAC1F,MAAM,CAAC,MAAM,gCAAgC,GAAG,CAAC,CAAC,YAAY,CAAC;IAC7D,cAAc,EAAE,cAAc;IAC9B,WAAW,EAAE,YAAY;CAC1B,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC;KACxC,MAAM,CAAC;IACN,QAAQ,EAAE,cAAc;IACxB,MAAM,EAAE,YAAY;IACpB,MAAM,EAAE,YAAY;IACpB,yEAAyE;IACzE,cAAc,EAAE,cAAc;IAC9B,yEAAyE;IACzE,wBAAwB,EAAE,qBAAqB;IAC/C,UAAU,EAAE,gCAAgC;IAC5C,sEAAsE;IACtE,YAAY,EAAE,WAAW;IACzB,6DAA6D;IAC7D,aAAa,EAAE,aAAa;IAC5B,MAAM,EAAE,gCAAgC,CAAC,QAAQ,EAAE;IACnD,4EAA4E;IAC5E,QAAQ,EAAE,aAAa,CAAC,QAAQ,EAAE;IAClC,iGAAiG;IACjG,8BAA8B;IAC9B,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC,QAAQ,EAAE;IACrF,SAAS,EAAE,aAAa;CACzB,CAAC;KACD,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,wBAAwB,IAAI,IAAI,CAAC,cAAc,EAAE;IACtE,OAAO,EAAE,iEAAiE;IAC1E,IAAI,EAAE,CAAC,0BAA0B,CAAC;CACnC,CAAC;KACD,MAAM,CACL,CAAC,IAAI,EAAE,EAAE,CACP,CAAC,IAAI,CAAC,UAAU,KAAK,6BAA6B,CAAC;IACnD,CAAC,IAAI,CAAC,wBAAwB,KAAK,IAAI,CAAC,cAAc,CAAC,EACzD;IACE,OAAO,EAAE,mFAAmF;IAC5F,IAAI,EAAE,CAAC,YAAY,CAAC;CACrB,CACF;KACA,MAAM,CACL,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,KAAK,yBAAyB,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC,EAC3F;IACE,OAAO,EAAE,8DAA8D;IACvE,IAAI,EAAE,CAAC,UAAU,CAAC;CACnB,CACF,CAAC","sourcesContent":["// `membershipRosterSyncs/{teamMembershipId}` — one team membership's durable obligation to bring\n// its team's open game rosters in line with it (QUALITY-104, BACKEND-302).\n//\n// Every change to a membership — made, removed, or linked — advances `rosterRevision` in the same\n// transaction as the change, so a committed membership can never lose its roster work. The roster\n// core raises `reconciledRosterRevision` to the revision it finished; the obligation is outstanding\n// while it is lower. The document outlives a deleted membership, so a removal is still reconciled,\n// and it is keyed by the membership id, so no second id format exists (ARCH-109). Backend-only.\n\nimport { z } from 'zod';\nimport { MAX_ROSTER_SYNC_FAILURE_MESSAGE_LENGTH } from '../constants/business.js';\nimport { FAILED_ROSTER_SYNC_STATUS, RECONCILED_ROSTER_SYNC_STATUS } from '../types/unions.js';\nimport {\n appliedRevisionSchema,\n countSchema,\n epochMsSchema,\n gameIdSchema,\n leagueIdSchema,\n membershipRosterSyncStatusSchema,\n revisionSchema,\n teamIdSchema,\n userIdSchema,\n} from './atoms.js';\n\n// Where a run that stopped part-way resumes: the last game it finished for this revision. A cursor\n// for an older revision is ignored, because a newer change re-reconciles every open game.\nexport const MembershipRosterSyncCursorSchema = z.strictObject({\n rosterRevision: revisionSchema,\n afterGameId: gameIdSchema,\n});\nexport type MembershipRosterSyncCursor = z.infer<typeof MembershipRosterSyncCursorSchema>;\n\nexport const MembershipRosterSyncSchema = z\n .object({\n leagueId: leagueIdSchema,\n teamId: teamIdSchema,\n userId: userIdSchema,\n // Advanced by every change to the membership; the first change writes 1.\n rosterRevision: revisionSchema,\n // The revision the roster core last finished; 0 before the first finish.\n reconciledRosterRevision: appliedRevisionSchema,\n syncStatus: membershipRosterSyncStatusSchema,\n // Runs of the worker at the current revision that ended in a failure.\n attemptCount: countSchema,\n // The earliest moment the worker takes the obligation again.\n nextAttemptAt: epochMsSchema,\n cursor: MembershipRosterSyncCursorSchema.optional(),\n // When its retries ran out: present exactly while the obligation is failed.\n failedAt: epochMsSchema.optional(),\n // The last failed run's message, kept while it retries and while it is failed; a finished run or\n // an admin's retry clears it.\n lastFailureMessage: z.string().max(MAX_ROSTER_SYNC_FAILURE_MESSAGE_LENGTH).optional(),\n updatedAt: epochMsSchema,\n })\n .refine((sync) => sync.reconciledRosterRevision <= sync.rosterRevision, {\n message: 'A roster obligation cannot be reconciled past its own revision.',\n path: ['reconciledRosterRevision'],\n })\n .refine(\n (sync) =>\n (sync.syncStatus === RECONCILED_ROSTER_SYNC_STATUS) ===\n (sync.reconciledRosterRevision === sync.rosterRevision),\n {\n message: 'An obligation is reconciled exactly when its reconciled revision is its revision.',\n path: ['syncStatus'],\n },\n )\n .refine(\n (sync) => (sync.syncStatus === FAILED_ROSTER_SYNC_STATUS) === (sync.failedAt !== undefined),\n {\n message: 'An obligation carries `failedAt` exactly while it is failed.',\n path: ['failedAt'],\n },\n );\nexport type MembershipRosterSync = z.infer<typeof MembershipRosterSyncSchema>;\n"]}
1
+ {"version":3,"file":"membership-roster-sync.js","sourceRoot":"","sources":["../../src/doc-schemas/membership-roster-sync.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,6FAA6F;AAC7F,EAAE;AACF,kGAAkG;AAClG,kGAAkG;AAClG,oGAAoG;AACpG,mGAAmG;AACnG,yGAAyG;AAEzG,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,sCAAsC,EAAE,MAAM,0BAA0B,CAAC;AAClF,OAAO,EAAE,yBAAyB,EAAE,6BAA6B,EAAE,MAAM,oBAAoB,CAAC;AAC9F,OAAO,EACL,qBAAqB,EACrB,WAAW,EACX,aAAa,EACb,YAAY,EACZ,cAAc,EACd,gCAAgC,EAChC,cAAc,EACd,YAAY,EACZ,YAAY,GACb,MAAM,YAAY,CAAC;AAEpB,mGAAmG;AACnG,0FAA0F;AAC1F,MAAM,CAAC,MAAM,gCAAgC,GAAG,CAAC,CAAC,YAAY,CAAC;IAC7D,cAAc,EAAE,cAAc;IAC9B,WAAW,EAAE,YAAY;CAC1B,CAAC,CAAC;AAGH,MAAM,CAAC,MAAM,0BAA0B,GAAG,CAAC;KACxC,MAAM,CAAC;IACN,QAAQ,EAAE,cAAc;IACxB,MAAM,EAAE,YAAY;IACpB,MAAM,EAAE,YAAY;IACpB,yEAAyE;IACzE,cAAc,EAAE,cAAc;IAC9B,yEAAyE;IACzE,wBAAwB,EAAE,qBAAqB;IAC/C,UAAU,EAAE,gCAAgC;IAC5C,sEAAsE;IACtE,YAAY,EAAE,WAAW;IACzB,6DAA6D;IAC7D,aAAa,EAAE,aAAa;IAC5B,MAAM,EAAE,gCAAgC,CAAC,QAAQ,EAAE;IACnD,4EAA4E;IAC5E,QAAQ,EAAE,aAAa,CAAC,QAAQ,EAAE;IAClC,iGAAiG;IACjG,8BAA8B;IAC9B,kBAAkB,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,sCAAsC,CAAC,CAAC,QAAQ,EAAE;IACrF,SAAS,EAAE,aAAa;CACzB,CAAC;KACD,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,wBAAwB,IAAI,IAAI,CAAC,cAAc,EAAE;IACtE,OAAO,EAAE,iEAAiE;IAC1E,IAAI,EAAE,CAAC,0BAA0B,CAAC;CACnC,CAAC;KACD,MAAM,CACL,CAAC,IAAI,EAAE,EAAE,CACP,CAAC,IAAI,CAAC,UAAU,KAAK,6BAA6B,CAAC;IACnD,CAAC,IAAI,CAAC,wBAAwB,KAAK,IAAI,CAAC,cAAc,CAAC,EACzD;IACE,OAAO,EAAE,mFAAmF;IAC5F,IAAI,EAAE,CAAC,YAAY,CAAC;CACrB,CACF;KACA,MAAM,CACL,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,UAAU,KAAK,yBAAyB,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,KAAK,SAAS,CAAC,EAC3F;IACE,OAAO,EAAE,8DAA8D;IACvE,IAAI,EAAE,CAAC,UAAU,CAAC;CACnB,CACF,CAAC","sourcesContent":["// `membershipRosterSyncs/{teamMembershipId}` — one team membership's durable obligation to bring\n// its team's open game rosters in line with it (RULE-STD-QUALITY-104, RULE-STD-BACKEND-302).\n//\n// Every change to a membership — made, removed, or linked — advances `rosterRevision` in the same\n// transaction as the change, so a committed membership can never lose its roster work. The roster\n// core raises `reconciledRosterRevision` to the revision it finished; the obligation is outstanding\n// while it is lower. The document outlives a deleted membership, so a removal is still reconciled,\n// and it is keyed by the membership id, so no second id format exists (RULE-STD-ARCH-109). Backend-only.\n\nimport { z } from 'zod';\nimport { MAX_ROSTER_SYNC_FAILURE_MESSAGE_LENGTH } from '../constants/business.js';\nimport { FAILED_ROSTER_SYNC_STATUS, RECONCILED_ROSTER_SYNC_STATUS } from '../types/unions.js';\nimport {\n appliedRevisionSchema,\n countSchema,\n epochMsSchema,\n gameIdSchema,\n leagueIdSchema,\n membershipRosterSyncStatusSchema,\n revisionSchema,\n teamIdSchema,\n userIdSchema,\n} from './atoms.js';\n\n// Where a run that stopped part-way resumes: the last game it finished for this revision. A cursor\n// for an older revision is ignored, because a newer change re-reconciles every open game.\nexport const MembershipRosterSyncCursorSchema = z.strictObject({\n rosterRevision: revisionSchema,\n afterGameId: gameIdSchema,\n});\nexport type MembershipRosterSyncCursor = z.infer<typeof MembershipRosterSyncCursorSchema>;\n\nexport const MembershipRosterSyncSchema = z\n .object({\n leagueId: leagueIdSchema,\n teamId: teamIdSchema,\n userId: userIdSchema,\n // Advanced by every change to the membership; the first change writes 1.\n rosterRevision: revisionSchema,\n // The revision the roster core last finished; 0 before the first finish.\n reconciledRosterRevision: appliedRevisionSchema,\n syncStatus: membershipRosterSyncStatusSchema,\n // Runs of the worker at the current revision that ended in a failure.\n attemptCount: countSchema,\n // The earliest moment the worker takes the obligation again.\n nextAttemptAt: epochMsSchema,\n cursor: MembershipRosterSyncCursorSchema.optional(),\n // When its retries ran out: present exactly while the obligation is failed.\n failedAt: epochMsSchema.optional(),\n // The last failed run's message, kept while it retries and while it is failed; a finished run or\n // an admin's retry clears it.\n lastFailureMessage: z.string().max(MAX_ROSTER_SYNC_FAILURE_MESSAGE_LENGTH).optional(),\n updatedAt: epochMsSchema,\n })\n .refine((sync) => sync.reconciledRosterRevision <= sync.rosterRevision, {\n message: 'A roster obligation cannot be reconciled past its own revision.',\n path: ['reconciledRosterRevision'],\n })\n .refine(\n (sync) =>\n (sync.syncStatus === RECONCILED_ROSTER_SYNC_STATUS) ===\n (sync.reconciledRosterRevision === sync.rosterRevision),\n {\n message: 'An obligation is reconciled exactly when its reconciled revision is its revision.',\n path: ['syncStatus'],\n },\n )\n .refine(\n (sync) => (sync.syncStatus === FAILED_ROSTER_SYNC_STATUS) === (sync.failedAt !== undefined),\n {\n message: 'An obligation carries `failedAt` exactly while it is failed.',\n path: ['failedAt'],\n },\n );\nexport type MembershipRosterSync = z.infer<typeof MembershipRosterSyncSchema>;\n"]}
@@ -440,6 +440,9 @@ export declare const PendingMediaPendingSchema: z.ZodObject<{
440
440
  }, z.core.$strict>;
441
441
  export declare const PendingMediaProcessingSchema: z.ZodObject<{
442
442
  status: z.ZodLiteral<"processing">;
443
+ processingStartedAt: z.ZodNumber;
444
+ processingAttemptCount: z.ZodNumber;
445
+ processingLeaseExpiresAt: z.ZodNumber;
443
446
  id: z.ZodString;
444
447
  userId: z.ZodString;
445
448
  fileOrigin: z.ZodEnum<{
@@ -484,9 +487,6 @@ export declare const PendingMediaProcessingSchema: z.ZodObject<{
484
487
  moderationOverride: z.ZodOptional<z.ZodString>;
485
488
  createdAt: z.ZodNumber;
486
489
  updatedAt: z.ZodNumber;
487
- processingStartedAt: z.ZodOptional<z.ZodNumber>;
488
- processingAttemptCount: z.ZodOptional<z.ZodNumber>;
489
- processingLeaseExpiresAt: z.ZodOptional<z.ZodNumber>;
490
490
  terminalAt: z.ZodOptional<z.ZodNumber>;
491
491
  }, z.core.$strict>;
492
492
  export declare const PendingMediaCompletedSchema: z.ZodObject<{
@@ -1382,6 +1382,9 @@ export declare const PendingMediaSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1382
1382
  terminalAt: z.ZodOptional<z.ZodNumber>;
1383
1383
  }, z.core.$strict>, z.ZodObject<{
1384
1384
  status: z.ZodLiteral<"processing">;
1385
+ processingStartedAt: z.ZodNumber;
1386
+ processingAttemptCount: z.ZodNumber;
1387
+ processingLeaseExpiresAt: z.ZodNumber;
1385
1388
  id: z.ZodString;
1386
1389
  userId: z.ZodString;
1387
1390
  fileOrigin: z.ZodEnum<{
@@ -1426,9 +1429,6 @@ export declare const PendingMediaSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1426
1429
  moderationOverride: z.ZodOptional<z.ZodString>;
1427
1430
  createdAt: z.ZodNumber;
1428
1431
  updatedAt: z.ZodNumber;
1429
- processingStartedAt: z.ZodOptional<z.ZodNumber>;
1430
- processingAttemptCount: z.ZodOptional<z.ZodNumber>;
1431
- processingLeaseExpiresAt: z.ZodOptional<z.ZodNumber>;
1432
1432
  terminalAt: z.ZodOptional<z.ZodNumber>;
1433
1433
  }, z.core.$strict>, z.ZodObject<{
1434
1434
  status: z.ZodLiteral<"completed">;
@@ -1,5 +1,5 @@
1
1
  // `publicUsers/{uid}` — display identity for real users AND ghosts, resolved at read time by
2
- // everything that shows a name (ARCH-103). `displayName_lowercase` powers prefix search.
2
+ // everything that shows a name (RULE-STD-ARCH-103). `displayName_lowercase` powers prefix search.
3
3
  import { z } from 'zod';
4
4
  import { epochMsSchema } from './atoms.js';
5
5
  export const PublicUserSchema = z.object({
@@ -1 +1 @@
1
- {"version":3,"file":"publicUser.js","sourceRoot":"","sources":["../../src/doc-schemas/publicUser.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,yFAAyF;AAEzF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IACf,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,qBAAqB,EAAE,CAAC,CAAC,MAAM,EAAE;IACjC,0FAA0F;IAC1F,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB,8DAA8D;IAC9D,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;IACrB,sDAAsD;IACtD,cAAc,EAAE,CAAC,CAAC,MAAM,CAAC;QACvB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC9B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KACjC,CAAC;IACF,SAAS,EAAE,aAAa;CACzB,CAAC,CAAC","sourcesContent":["// `publicUsers/{uid}` — display identity for real users AND ghosts, resolved at read time by\n// everything that shows a name (ARCH-103). `displayName_lowercase` powers prefix search.\n\nimport { z } from 'zod';\nimport { epochMsSchema } from './atoms.js';\n\nexport const PublicUserSchema = z.object({\n uid: z.string(),\n displayName: z.string(),\n displayName_lowercase: z.string(),\n // True for a `ghost_*` placeholder player; derived from the id, stored for query filters.\n isGhost: z.boolean(),\n // True while the account is banned; always false for a ghost.\n disabled: z.boolean(),\n // Award ids the user chose to show beside their name.\n selectedBadges: z.object({\n primary: z.string().optional(),\n secondary: z.string().optional(),\n }),\n updatedAt: epochMsSchema,\n});\nexport type PublicUser = z.infer<typeof PublicUserSchema>;\n"]}
1
+ {"version":3,"file":"publicUser.js","sourceRoot":"","sources":["../../src/doc-schemas/publicUser.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,kGAAkG;AAElG,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,CAAC,MAAM,CAAC;IACvC,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;IACf,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,qBAAqB,EAAE,CAAC,CAAC,MAAM,EAAE;IACjC,0FAA0F;IAC1F,OAAO,EAAE,CAAC,CAAC,OAAO,EAAE;IACpB,8DAA8D;IAC9D,QAAQ,EAAE,CAAC,CAAC,OAAO,EAAE;IACrB,sDAAsD;IACtD,cAAc,EAAE,CAAC,CAAC,MAAM,CAAC;QACvB,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;QAC9B,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KACjC,CAAC;IACF,SAAS,EAAE,aAAa;CACzB,CAAC,CAAC","sourcesContent":["// `publicUsers/{uid}` — display identity for real users AND ghosts, resolved at read time by\n// everything that shows a name (RULE-STD-ARCH-103). `displayName_lowercase` powers prefix search.\n\nimport { z } from 'zod';\nimport { epochMsSchema } from './atoms.js';\n\nexport const PublicUserSchema = z.object({\n uid: z.string(),\n displayName: z.string(),\n displayName_lowercase: z.string(),\n // True for a `ghost_*` placeholder player; derived from the id, stored for query filters.\n isGhost: z.boolean(),\n // True while the account is banned; always false for a ghost.\n disabled: z.boolean(),\n // Award ids the user chose to show beside their name.\n selectedBadges: z.object({\n primary: z.string().optional(),\n secondary: z.string().optional(),\n }),\n updatedAt: epochMsSchema,\n});\nexport type PublicUser = z.infer<typeof PublicUserSchema>;\n"]}
@@ -2,7 +2,7 @@
2
2
  // `createRegistrationStart` right after the login is created, before `registerUser`. It is the
3
3
  // orphan-login cleanup's work list: a login whose registration started more than the grace window
4
4
  // ago and still holds no profile was abandoned. Backend-only; no client reads or writes it. Its
5
- // `expireAt` is the native-TTL field that removes it (ARCH-105's exception).
5
+ // `expireAt` is the native-TTL field that removes it (RULE-STD-ARCH-105's exception).
6
6
  import { z } from 'zod';
7
7
  import { epochMsSchema, userIdSchema } from './atoms.js';
8
8
  import { FirestoreTimestampSchema } from './firestore-primitives.js';
@@ -1 +1 @@
1
- {"version":3,"file":"registration-start.js","sourceRoot":"","sources":["../../src/doc-schemas/registration-start.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,+FAA+F;AAC/F,kGAAkG;AAClG,gGAAgG;AAChG,6EAA6E;AAE7E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAErE,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC;KACrC,MAAM,CAAC;IACN,MAAM,EAAE,YAAY;IACpB,SAAS,EAAE,aAAa;IACxB,QAAQ,EAAE,wBAAwB;CACnC,CAAC;KACD,MAAM,EAAE,CAAC","sourcesContent":["// `registrationStarts/{userId}` — the record that a login's registration started: written by\n// `createRegistrationStart` right after the login is created, before `registerUser`. It is the\n// orphan-login cleanup's work list: a login whose registration started more than the grace window\n// ago and still holds no profile was abandoned. Backend-only; no client reads or writes it. Its\n// `expireAt` is the native-TTL field that removes it (ARCH-105's exception).\n\nimport { z } from 'zod';\nimport { epochMsSchema, userIdSchema } from './atoms.js';\nimport { FirestoreTimestampSchema } from './firestore-primitives.js';\n\nexport const RegistrationStartSchema = z\n .object({\n userId: userIdSchema,\n startedAt: epochMsSchema,\n expireAt: FirestoreTimestampSchema,\n })\n .strict();\nexport type RegistrationStart = z.infer<typeof RegistrationStartSchema>;\n"]}
1
+ {"version":3,"file":"registration-start.js","sourceRoot":"","sources":["../../src/doc-schemas/registration-start.ts"],"names":[],"mappings":"AAAA,6FAA6F;AAC7F,+FAA+F;AAC/F,kGAAkG;AAClG,gGAAgG;AAChG,sFAAsF;AAEtF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AACzD,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAC;AAErE,MAAM,CAAC,MAAM,uBAAuB,GAAG,CAAC;KACrC,MAAM,CAAC;IACN,MAAM,EAAE,YAAY;IACpB,SAAS,EAAE,aAAa;IACxB,QAAQ,EAAE,wBAAwB;CACnC,CAAC;KACD,MAAM,EAAE,CAAC","sourcesContent":["// `registrationStarts/{userId}` — the record that a login's registration started: written by\n// `createRegistrationStart` right after the login is created, before `registerUser`. It is the\n// orphan-login cleanup's work list: a login whose registration started more than the grace window\n// ago and still holds no profile was abandoned. Backend-only; no client reads or writes it. Its\n// `expireAt` is the native-TTL field that removes it (RULE-STD-ARCH-105's exception).\n\nimport { z } from 'zod';\nimport { epochMsSchema, userIdSchema } from './atoms.js';\nimport { FirestoreTimestampSchema } from './firestore-primitives.js';\n\nexport const RegistrationStartSchema = z\n .object({\n userId: userIdSchema,\n startedAt: epochMsSchema,\n expireAt: FirestoreTimestampSchema,\n })\n .strict();\nexport type RegistrationStart = z.infer<typeof RegistrationStartSchema>;\n"]}
@@ -1626,6 +1626,9 @@ export declare const COLLECTION_SCHEMAS: {
1626
1626
  terminalAt: z.ZodOptional<z.ZodNumber>;
1627
1627
  }, z.core.$strict>, z.ZodObject<{
1628
1628
  status: z.ZodLiteral<"processing">;
1629
+ processingStartedAt: z.ZodNumber;
1630
+ processingAttemptCount: z.ZodNumber;
1631
+ processingLeaseExpiresAt: z.ZodNumber;
1629
1632
  id: z.ZodString;
1630
1633
  userId: z.ZodString;
1631
1634
  fileOrigin: z.ZodEnum<{
@@ -1670,9 +1673,6 @@ export declare const COLLECTION_SCHEMAS: {
1670
1673
  moderationOverride: z.ZodOptional<z.ZodString>;
1671
1674
  createdAt: z.ZodNumber;
1672
1675
  updatedAt: z.ZodNumber;
1673
- processingStartedAt: z.ZodOptional<z.ZodNumber>;
1674
- processingAttemptCount: z.ZodOptional<z.ZodNumber>;
1675
- processingLeaseExpiresAt: z.ZodOptional<z.ZodNumber>;
1676
1676
  terminalAt: z.ZodOptional<z.ZodNumber>;
1677
1677
  }, z.core.$strict>, z.ZodObject<{
1678
1678
  status: z.ZodLiteral<"completed">;
@@ -1,12 +1,12 @@
1
1
  // `sportGames/{gameId}` — one scheduled or played game — and its `gameRosterPlayers` subcollection.
2
- // The game is discriminated on `leagueType`, which selects its `stats` shape (SPORT-002).
2
+ // The game is discriminated on `leagueType`, which selects its `stats` shape (RULE-QS-SPORT-902).
3
3
  import { z } from 'zod';
4
4
  import { parseGameRosterPlayerId } from '../ids/compound-ids.js';
5
5
  import { SPORT_LINE_SCHEMAS } from './aggregate-stats.js';
6
6
  import { gameStatsSchema } from './game-stats.js';
7
7
  import { calendarDateSchema, countSchema, epochMsSchema, gameRosterPlayerIdSchema, gameStatusSchema, leagueIdSchema, scheduleWeekIdSchema, statsOperationIdSchema, teamIdSchema, userIdSchema, } from './atoms.js';
8
8
  import { gameLocationIdSchema } from './game-location.js';
9
- // Each sport's game `stats`: the two-key payload (SPORT-001) over that sport's game lines, taken
9
+ // Each sport's game `stats`: the two-key payload (RULE-QS-SPORT-901) over that sport's game lines, taken
10
10
  // from `SPORT_LINE_SCHEMAS` — the one pairing of a sport with its lines. Spelled per sport so each
11
11
  // payload keeps its exact type; `satisfies` fails the build when a sport is missing.
12
12
  export const SPORT_GAME_STATS_SCHEMAS = {
@@ -38,7 +38,7 @@ const sportGameBase = {
38
38
  // The real (non-bye) team ids, for `array-contains` queries.
39
39
  teamIds: z.array(teamIdSchema).min(1).max(2),
40
40
  // Each team's score, mirrored from its `stats.teamStats` line by the same write that changes it
41
- // (SPORT-107), so a schedule or list reads a score without the stats.
41
+ // (RULE-QS-SPORT-917), so a schedule or list reads a score without the stats.
42
42
  score: z.record(teamIdSchema, countSchema),
43
43
  // The number of this game's stat event documents, which is also the last `sequence` assigned.
44
44
  statEventSequence: countSchema,
@@ -47,7 +47,7 @@ const sportGameBase = {
47
47
  // Absence is per roster entry: a person on both teams can be absent on one side only.
48
48
  absentRosterPlayerIds: z.array(gameRosterPlayerIdSchema),
49
49
  // The roster entries finalize credited no game played — the adapter's decision, stored so every
50
- // later path reverses what was applied rather than deciding again (SPORT-204). Present only while
50
+ // later path reverses what was applied rather than deciding again (RULE-QS-SPORT-924). Present only while
51
51
  // the game is completed.
52
52
  nonScoringRosterPlayerIds: z.array(gameRosterPlayerIdSchema).optional(),
53
53
  // Set while a multi-page stats operation holds the game; every other writer of its stats, roster,
@@ -1 +1 @@
1
- {"version":3,"file":"sport-game.js","sourceRoot":"","sources":["../../src/doc-schemas/sport-game.ts"],"names":[],"mappings":"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","sourcesContent":["// `sportGames/{gameId}` — one scheduled or played game — and its `gameRosterPlayers` subcollection.\n// The game is discriminated on `leagueType`, which selects its `stats` shape (SPORT-002).\n\nimport { z } from 'zod';\nimport { parseGameRosterPlayerId } from '../ids/compound-ids.js';\nimport type { LeagueType } from '../types/sports.js';\nimport { SPORT_LINE_SCHEMAS } from './aggregate-stats.js';\nimport { gameStatsSchema } from './game-stats.js';\nimport {\n calendarDateSchema,\n countSchema,\n epochMsSchema,\n gameRosterPlayerIdSchema,\n gameStatusSchema,\n leagueIdSchema,\n scheduleWeekIdSchema,\n statsOperationIdSchema,\n teamIdSchema,\n userIdSchema,\n} from './atoms.js';\nimport { gameLocationIdSchema } from './game-location.js';\n\n// Each sport's game `stats`: the two-key payload (SPORT-001) over that sport's game lines, taken\n// from `SPORT_LINE_SCHEMAS` — the one pairing of a sport with its lines. Spelled per sport so each\n// payload keeps its exact type; `satisfies` fails the build when a sport is missing.\nexport const SPORT_GAME_STATS_SCHEMAS = {\n Basketball: gameStatsSchema(\n SPORT_LINE_SCHEMAS.Basketball.gameTeam,\n SPORT_LINE_SCHEMAS.Basketball.gamePlayer,\n ),\n Curling: gameStatsSchema(\n SPORT_LINE_SCHEMAS.Curling.gameTeam,\n SPORT_LINE_SCHEMAS.Curling.gamePlayer,\n ),\n Football: gameStatsSchema(\n SPORT_LINE_SCHEMAS.Football.gameTeam,\n SPORT_LINE_SCHEMAS.Football.gamePlayer,\n ),\n} as const satisfies Record<LeagueType, z.ZodType>;\n\nexport type BasketballGameStats = z.infer<typeof SPORT_GAME_STATS_SCHEMAS.Basketball>;\nexport type CurlingGameStats = z.infer<typeof SPORT_GAME_STATS_SCHEMAS.Curling>;\nexport type FootballGameStats = z.infer<typeof SPORT_GAME_STATS_SCHEMAS.Football>;\n\n// Which sport's payload a game holds is decided by its `leagueType`, never by the payload's shape\n// (SPORT-002).\nexport type GameStats = z.infer<(typeof SPORT_GAME_STATS_SCHEMAS)[LeagueType]>;\n\n// One of a game's two team slots. `teamId` is a leagueTeams id, or BYE_TEAM_ID for a bye week.\nexport const GameTeamSchema = z.object({\n teamId: teamIdSchema,\n});\nexport type GameTeam = z.infer<typeof GameTeamSchema>;\n\nconst sportGameBase = {\n leagueId: leagueIdSchema,\n // The week this game belongs to; weekly schedules are reconstructed by grouping on `weekId`.\n weekId: scheduleWeekIdSchema,\n weekTitle: z.string(),\n weekLocation: z.string(),\n weekStartDate: epochMsSchema,\n startTime: epochMsSchema,\n // The game's local calendar date in LEAGUE_TIME_ZONE.\n dayKey: calendarDateSchema,\n // The venue as shown, e.g. \"Main Gym - Court 2\". Free text; `gameLocationId` is the reference.\n locationLabel: z.string(),\n gameLocationId: gameLocationIdSchema.optional(),\n subLocation: z.string().optional(),\n status: gameStatusSchema,\n // Positional: teams[0] is team one, teams[1] is team two.\n teams: z.tuple([GameTeamSchema, GameTeamSchema]),\n // The real (non-bye) team ids, for `array-contains` queries.\n teamIds: z.array(teamIdSchema).min(1).max(2),\n // Each team's score, mirrored from its `stats.teamStats` line by the same write that changes it\n // (SPORT-107), so a schedule or list reads a score without the stats.\n score: z.record(teamIdSchema, countSchema),\n // The number of this game's stat event documents, which is also the last `sequence` assigned.\n statEventSequence: countSchema,\n // A scorer's note per person, keyed by userId (real or ghost).\n memos: z.record(z.string(), z.string()).optional(),\n // Absence is per roster entry: a person on both teams can be absent on one side only.\n absentRosterPlayerIds: z.array(gameRosterPlayerIdSchema),\n // The roster entries finalize credited no game played — the adapter's decision, stored so every\n // later path reverses what was applied rather than deciding again (SPORT-204). Present only while\n // the game is completed.\n nonScoringRosterPlayerIds: z.array(gameRosterPlayerIdSchema).optional(),\n // Set while a multi-page stats operation holds the game; every other writer of its stats, roster,\n // absence, memos, teams, or existence refuses it until the operation clears it.\n statsOperationId: statsOperationIdSchema.optional(),\n finalizedAt: epochMsSchema.optional(),\n // When reopen last opened the finalized game again; set by reopen and never cleared. Its roster is\n // then the record of who played it, so a membership change no longer reaches it — only a scorer\n // adding a ghost to this game does.\n reopenedAt: epochMsSchema.optional(),\n createdAt: epochMsSchema,\n updatedAt: epochMsSchema,\n};\n\n// The rollup holds a line for each of the game's teams and no other, the score mirror names the same\n// teams, and every player line is keyed to one of them.\nfunction rollupMatchesTeams(game: {\n teamIds: string[];\n score: Record<string, number>;\n stats: { teamStats: Record<string, unknown>; playerStats: Record<string, unknown> };\n}): boolean {\n const teams = new Set(game.teamIds);\n const namesEveryTeam = (keys: string[]) =>\n keys.length === teams.size && keys.every((key) => teams.has(key));\n return (\n namesEveryTeam(Object.keys(game.stats.teamStats)) &&\n namesEveryTeam(Object.keys(game.score)) &&\n Object.keys(game.stats.playerStats).every((key) =>\n teams.has(parseGameRosterPlayerId(key)?.teamId ?? ''),\n )\n );\n}\nconst ROLLUP_MISMATCH = \"The stats rollup and score mirror hold exactly the game's teams.\";\n\nexport const BasketballGameSchema = z\n .object({\n ...sportGameBase,\n leagueType: z.literal('Basketball' satisfies LeagueType),\n stats: SPORT_GAME_STATS_SCHEMAS.Basketball,\n // Which team holds the possession arrow.\n jumpBallTeamId: teamIdSchema.optional(),\n })\n .refine(rollupMatchesTeams, ROLLUP_MISMATCH);\nexport type BasketballGame = z.infer<typeof BasketballGameSchema>;\n\nexport const CurlingGameSchema = z\n .object({\n ...sportGameBase,\n leagueType: z.literal('Curling' satisfies LeagueType),\n stats: SPORT_GAME_STATS_SCHEMAS.Curling,\n })\n .refine(rollupMatchesTeams, ROLLUP_MISMATCH);\nexport type CurlingGame = z.infer<typeof CurlingGameSchema>;\n\nexport const FootballGameSchema = z\n .object({\n ...sportGameBase,\n leagueType: z.literal('Football' satisfies LeagueType),\n stats: SPORT_GAME_STATS_SCHEMAS.Football,\n })\n .refine(rollupMatchesTeams, ROLLUP_MISMATCH);\nexport type FootballGame = z.infer<typeof FootballGameSchema>;\n\nexport const SportGameSchema = z.discriminatedUnion('leagueType', [\n BasketballGameSchema,\n CurlingGameSchema,\n FootballGameSchema,\n]);\nexport type SportGame = z.infer<typeof SportGameSchema>;\n\n// `sportGames/{gameId}/gameRosterPlayers/{teamId}_{userId}` — one entry per team per person, the id\n// from `buildGameRosterPlayerId`. The roster is the participation authority finalize iterates.\nexport const GameRosterPlayerSchema = z.object({\n userId: userIdSchema,\n teamId: teamIdSchema,\n isGhost: z.boolean(),\n syncedAt: epochMsSchema,\n});\nexport type GameRosterPlayer = z.infer<typeof GameRosterPlayerSchema>;\n"]}
1
+ {"version":3,"file":"sport-game.js","sourceRoot":"","sources":["../../src/doc-schemas/sport-game.ts"],"names":[],"mappings":"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","sourcesContent":["// `sportGames/{gameId}` — one scheduled or played game — and its `gameRosterPlayers` subcollection.\n// The game is discriminated on `leagueType`, which selects its `stats` shape (RULE-QS-SPORT-902).\n\nimport { z } from 'zod';\nimport { parseGameRosterPlayerId } from '../ids/compound-ids.js';\nimport type { LeagueType } from '../types/sports.js';\nimport { SPORT_LINE_SCHEMAS } from './aggregate-stats.js';\nimport { gameStatsSchema } from './game-stats.js';\nimport {\n calendarDateSchema,\n countSchema,\n epochMsSchema,\n gameRosterPlayerIdSchema,\n gameStatusSchema,\n leagueIdSchema,\n scheduleWeekIdSchema,\n statsOperationIdSchema,\n teamIdSchema,\n userIdSchema,\n} from './atoms.js';\nimport { gameLocationIdSchema } from './game-location.js';\n\n// Each sport's game `stats`: the two-key payload (RULE-QS-SPORT-901) over that sport's game lines, taken\n// from `SPORT_LINE_SCHEMAS` — the one pairing of a sport with its lines. Spelled per sport so each\n// payload keeps its exact type; `satisfies` fails the build when a sport is missing.\nexport const SPORT_GAME_STATS_SCHEMAS = {\n Basketball: gameStatsSchema(\n SPORT_LINE_SCHEMAS.Basketball.gameTeam,\n SPORT_LINE_SCHEMAS.Basketball.gamePlayer,\n ),\n Curling: gameStatsSchema(\n SPORT_LINE_SCHEMAS.Curling.gameTeam,\n SPORT_LINE_SCHEMAS.Curling.gamePlayer,\n ),\n Football: gameStatsSchema(\n SPORT_LINE_SCHEMAS.Football.gameTeam,\n SPORT_LINE_SCHEMAS.Football.gamePlayer,\n ),\n} as const satisfies Record<LeagueType, z.ZodType>;\n\nexport type BasketballGameStats = z.infer<typeof SPORT_GAME_STATS_SCHEMAS.Basketball>;\nexport type CurlingGameStats = z.infer<typeof SPORT_GAME_STATS_SCHEMAS.Curling>;\nexport type FootballGameStats = z.infer<typeof SPORT_GAME_STATS_SCHEMAS.Football>;\n\n// Which sport's payload a game holds is decided by its `leagueType`, never by the payload's shape\n// (RULE-QS-SPORT-902).\nexport type GameStats = z.infer<(typeof SPORT_GAME_STATS_SCHEMAS)[LeagueType]>;\n\n// One of a game's two team slots. `teamId` is a leagueTeams id, or BYE_TEAM_ID for a bye week.\nexport const GameTeamSchema = z.object({\n teamId: teamIdSchema,\n});\nexport type GameTeam = z.infer<typeof GameTeamSchema>;\n\nconst sportGameBase = {\n leagueId: leagueIdSchema,\n // The week this game belongs to; weekly schedules are reconstructed by grouping on `weekId`.\n weekId: scheduleWeekIdSchema,\n weekTitle: z.string(),\n weekLocation: z.string(),\n weekStartDate: epochMsSchema,\n startTime: epochMsSchema,\n // The game's local calendar date in LEAGUE_TIME_ZONE.\n dayKey: calendarDateSchema,\n // The venue as shown, e.g. \"Main Gym - Court 2\". Free text; `gameLocationId` is the reference.\n locationLabel: z.string(),\n gameLocationId: gameLocationIdSchema.optional(),\n subLocation: z.string().optional(),\n status: gameStatusSchema,\n // Positional: teams[0] is team one, teams[1] is team two.\n teams: z.tuple([GameTeamSchema, GameTeamSchema]),\n // The real (non-bye) team ids, for `array-contains` queries.\n teamIds: z.array(teamIdSchema).min(1).max(2),\n // Each team's score, mirrored from its `stats.teamStats` line by the same write that changes it\n // (RULE-QS-SPORT-917), so a schedule or list reads a score without the stats.\n score: z.record(teamIdSchema, countSchema),\n // The number of this game's stat event documents, which is also the last `sequence` assigned.\n statEventSequence: countSchema,\n // A scorer's note per person, keyed by userId (real or ghost).\n memos: z.record(z.string(), z.string()).optional(),\n // Absence is per roster entry: a person on both teams can be absent on one side only.\n absentRosterPlayerIds: z.array(gameRosterPlayerIdSchema),\n // The roster entries finalize credited no game played — the adapter's decision, stored so every\n // later path reverses what was applied rather than deciding again (RULE-QS-SPORT-924). Present only while\n // the game is completed.\n nonScoringRosterPlayerIds: z.array(gameRosterPlayerIdSchema).optional(),\n // Set while a multi-page stats operation holds the game; every other writer of its stats, roster,\n // absence, memos, teams, or existence refuses it until the operation clears it.\n statsOperationId: statsOperationIdSchema.optional(),\n finalizedAt: epochMsSchema.optional(),\n // When reopen last opened the finalized game again; set by reopen and never cleared. Its roster is\n // then the record of who played it, so a membership change no longer reaches it — only a scorer\n // adding a ghost to this game does.\n reopenedAt: epochMsSchema.optional(),\n createdAt: epochMsSchema,\n updatedAt: epochMsSchema,\n};\n\n// The rollup holds a line for each of the game's teams and no other, the score mirror names the same\n// teams, and every player line is keyed to one of them.\nfunction rollupMatchesTeams(game: {\n teamIds: string[];\n score: Record<string, number>;\n stats: { teamStats: Record<string, unknown>; playerStats: Record<string, unknown> };\n}): boolean {\n const teams = new Set(game.teamIds);\n const namesEveryTeam = (keys: string[]) =>\n keys.length === teams.size && keys.every((key) => teams.has(key));\n return (\n namesEveryTeam(Object.keys(game.stats.teamStats)) &&\n namesEveryTeam(Object.keys(game.score)) &&\n Object.keys(game.stats.playerStats).every((key) =>\n teams.has(parseGameRosterPlayerId(key)?.teamId ?? ''),\n )\n );\n}\nconst ROLLUP_MISMATCH = \"The stats rollup and score mirror hold exactly the game's teams.\";\n\nexport const BasketballGameSchema = z\n .object({\n ...sportGameBase,\n leagueType: z.literal('Basketball' satisfies LeagueType),\n stats: SPORT_GAME_STATS_SCHEMAS.Basketball,\n // Which team holds the possession arrow.\n jumpBallTeamId: teamIdSchema.optional(),\n })\n .refine(rollupMatchesTeams, ROLLUP_MISMATCH);\nexport type BasketballGame = z.infer<typeof BasketballGameSchema>;\n\nexport const CurlingGameSchema = z\n .object({\n ...sportGameBase,\n leagueType: z.literal('Curling' satisfies LeagueType),\n stats: SPORT_GAME_STATS_SCHEMAS.Curling,\n })\n .refine(rollupMatchesTeams, ROLLUP_MISMATCH);\nexport type CurlingGame = z.infer<typeof CurlingGameSchema>;\n\nexport const FootballGameSchema = z\n .object({\n ...sportGameBase,\n leagueType: z.literal('Football' satisfies LeagueType),\n stats: SPORT_GAME_STATS_SCHEMAS.Football,\n })\n .refine(rollupMatchesTeams, ROLLUP_MISMATCH);\nexport type FootballGame = z.infer<typeof FootballGameSchema>;\n\nexport const SportGameSchema = z.discriminatedUnion('leagueType', [\n BasketballGameSchema,\n CurlingGameSchema,\n FootballGameSchema,\n]);\nexport type SportGame = z.infer<typeof SportGameSchema>;\n\n// `sportGames/{gameId}/gameRosterPlayers/{teamId}_{userId}` — one entry per team per person, the id\n// from `buildGameRosterPlayerId`. The roster is the participation authority finalize iterates.\nexport const GameRosterPlayerSchema = z.object({\n userId: userIdSchema,\n teamId: teamIdSchema,\n isGhost: z.boolean(),\n syncedAt: epochMsSchema,\n});\nexport type GameRosterPlayer = z.infer<typeof GameRosterPlayerSchema>;\n"]}
@@ -61,7 +61,7 @@ export const SportLeagueSchema = z
61
61
  // Advanced by every team created in the league; absent until the first. Only its changing
62
62
  // matters: team creation counts the league's teams in its transaction against
63
63
  // MAX_TEAMS_PER_LEAGUE, and a query does not lock against an insert — so each creation writes this
64
- // document, and two creations at the cap conflict instead of both passing (BACKEND-116).
64
+ // document, and two creations at the cap conflict instead of both passing (RULE-STD-BACKEND-116).
65
65
  teamRevision: countSchema.optional(),
66
66
  // Advanced by every game created in the league, a schedule week's included; absent until the
67
67
  // first. Only its changing matters, the same way: game creation counts the league's games in its
@@ -1 +1 @@
1
- {"version":3,"file":"sport-league.js","sourceRoot":"","sources":["../../src/doc-schemas/sport-league.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,iGAAiG;AACjG,gGAAgG;AAChG,0BAA0B;AAE1B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,MAAM,CAAC,MAAM,iCAAiC,GAAG,yCAAyC,CAAC;AAC3F,MAAM,CAAC,MAAM,mCAAmC,GAC9C,wFAAwF,CAAC;AAE3F;;;GAGG;AACH,MAAM,UAAU,8BAA8B,CAAC,MAG9C;IACC,OAAO,MAAM,CAAC,cAAc,KAAK,CAAC,MAAM,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC;AAC3E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAOlC,MAAS,EAAE,GAA6B;IACxC,IAAI,MAAM,CAAC,aAAa,GAAG,MAAM,CAAC,eAAe,EAAE,CAAC;QAClD,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,eAAe,CAAC;YACvB,OAAO,EAAE,iCAAiC;SAC3C,CAAC,CAAC;IACL,CAAC;IACD,IAAI,CAAC,8BAA8B,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5C,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,kBAAkB,CAAC;YAC1B,OAAO,EAAE,mCAAmC;SAC7C,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,wGAAwG;AACxG,MAAM,CAAC,MAAM,wBAAwB,GAAG,WAAW,CAAC;AAEpD,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC;KAC/B,MAAM,CAAC;IACN,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,UAAU,EAAE,gBAAgB;IAC5B,UAAU,EAAE,gBAAgB;IAC5B,eAAe,EAAE,kBAAkB;IACnC,aAAa,EAAE,kBAAkB;IACjC,MAAM,EAAE,kBAAkB;IAC1B,8FAA8F;IAC9F,gGAAgG;IAChG,qCAAqC;IACrC,aAAa,EAAE,wBAAwB;IACvC,iBAAiB,EAAE,wBAAwB;IAC3C,gBAAgB,EAAE,wBAAwB;IAC1C,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE;IAC3B,gBAAgB,EAAE,sBAAsB,CAAC,QAAQ,EAAE;IACnD,2FAA2F;IAC3F,gEAAgE;IAChE,QAAQ,EAAE,oBAAoB,CAAC,QAAQ,EAAE;IACzC,0FAA0F;IAC1F,8EAA8E;IAC9E,mGAAmG;IACnG,yFAAyF;IACzF,YAAY,EAAE,WAAW,CAAC,QAAQ,EAAE;IACpC,6FAA6F;IAC7F,iGAAiG;IACjG,4CAA4C;IAC5C,YAAY,EAAE,WAAW,CAAC,QAAQ,EAAE;IACpC,SAAS,EAAE,aAAa;IACxB,SAAS,EAAE,aAAa;CACzB,CAAC;KACD,WAAW,CAAC,oBAAoB,CAAC,CAAC","sourcesContent":["// `sportLeagues/{leagueId}` — one league: its sport, mode, dates, status, playoff cutoffs, and waiver\n// requirement. The admin league callables write its settings, status, and mode; the picture core\n// writes `pictures`. Teams, games, and player stats live in their own collections and are never\n// copied onto the league.\n\nimport { z } from 'zod';\nimport {\n calendarDateSchema,\n countSchema,\n epochMsSchema,\n leagueModeSchema,\n leagueStatusSchema,\n leagueTypeSchema,\n} from './atoms.js';\nimport { GallerySummarySchema } from './gallery.js';\nimport { waiverTemplateIdSchema } from './waiver-template.js';\n\nexport const LEAGUE_DATES_OUT_OF_ORDER_MESSAGE = 'The league cannot end before it starts.';\nexport const WAIVER_REQUIREMENT_MISMATCH_MESSAGE =\n 'Choose a waiver template when the league requires a waiver, and none when it does not.';\n\n/**\n * A league names a waiver template exactly when it requires a waiver, so no reader needs a\n * sentinel for \"no waiver\" and no stale template id survives turning the requirement off.\n */\nexport function hasConsistentWaiverRequirement(league: {\n requiresWaiver: boolean;\n waiverTemplateId?: string | undefined;\n}): boolean {\n return league.requiresWaiver === (league.waiverTemplateId !== undefined);\n}\n\n/**\n * The cross-field rules every league settings shape obeys — the stored document and both callable\n * inputs — so the rule a form shows, the callable enforces, and the copy action validates is one\n * rule.\n */\nexport function refineLeagueSettings<\n T extends {\n leagueStartDate: string;\n leagueEndDate: string;\n requiresWaiver: boolean;\n waiverTemplateId?: string | undefined;\n },\n>(league: T, ctx: z.core.$RefinementCtx<T>): void {\n if (league.leagueEndDate < league.leagueStartDate) {\n ctx.addIssue({\n code: 'custom',\n path: ['leagueEndDate'],\n message: LEAGUE_DATES_OUT_OF_ORDER_MESSAGE,\n });\n }\n if (!hasConsistentWaiverRequirement(league)) {\n ctx.addIssue({\n code: 'custom',\n path: ['waiverTemplateId'],\n message: WAIVER_REQUIREMENT_MISMATCH_MESSAGE,\n });\n }\n}\n\n// How many teams reach the playoffs is a count; the callable inputs bound it with `MAX_PLAYOFF_CUTOFF`.\nexport const playoffCutoffCountSchema = countSchema;\n\nexport const SportLeagueSchema = z\n .object({\n leagueTitle: z.string(),\n description: z.string(),\n leagueType: leagueTypeSchema,\n leagueMode: leagueModeSchema,\n leagueStartDate: calendarDateSchema,\n leagueEndDate: calendarDateSchema,\n status: leagueStatusSchema,\n // How many teams reach the playoffs: `playoffCutoff` in a Regular league, the Competitive and\n // Recreational pools' own cutoffs in a Competitive/Recreational one. All three are kept in both\n // modes so a mode switch loses none.\n playoffCutoff: playoffCutoffCountSchema,\n playoffCutoffComp: playoffCutoffCountSchema,\n playoffCutoffRec: playoffCutoffCountSchema,\n requiresWaiver: z.boolean(),\n waiverTemplateId: waiverTemplateIdSchema.optional(),\n // The league's picture gallery summary — absent is an empty gallery. Only the picture core\n // writes it; the league callables and the copy action never do.\n pictures: GallerySummarySchema.optional(),\n // Advanced by every team created in the league; absent until the first. Only its changing\n // matters: team creation counts the league's teams in its transaction against\n // MAX_TEAMS_PER_LEAGUE, and a query does not lock against an insert — so each creation writes this\n // document, and two creations at the cap conflict instead of both passing (BACKEND-116).\n teamRevision: countSchema.optional(),\n // Advanced by every game created in the league, a schedule week's included; absent until the\n // first. Only its changing matters, the same way: game creation counts the league's games in its\n // transaction against MAX_GAMES_PER_LEAGUE.\n gameRevision: countSchema.optional(),\n createdAt: epochMsSchema,\n updatedAt: epochMsSchema,\n })\n .superRefine(refineLeagueSettings);\nexport type SportLeague = z.infer<typeof SportLeagueSchema>;\n"]}
1
+ {"version":3,"file":"sport-league.js","sourceRoot":"","sources":["../../src/doc-schemas/sport-league.ts"],"names":[],"mappings":"AAAA,sGAAsG;AACtG,iGAAiG;AACjG,gGAAgG;AAChG,0BAA0B;AAE1B,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EACL,kBAAkB,EAClB,WAAW,EACX,aAAa,EACb,gBAAgB,EAChB,kBAAkB,EAClB,gBAAgB,GACjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,MAAM,CAAC,MAAM,iCAAiC,GAAG,yCAAyC,CAAC;AAC3F,MAAM,CAAC,MAAM,mCAAmC,GAC9C,wFAAwF,CAAC;AAE3F;;;GAGG;AACH,MAAM,UAAU,8BAA8B,CAAC,MAG9C;IACC,OAAO,MAAM,CAAC,cAAc,KAAK,CAAC,MAAM,CAAC,gBAAgB,KAAK,SAAS,CAAC,CAAC;AAC3E,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,oBAAoB,CAOlC,MAAS,EAAE,GAA6B;IACxC,IAAI,MAAM,CAAC,aAAa,GAAG,MAAM,CAAC,eAAe,EAAE,CAAC;QAClD,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,eAAe,CAAC;YACvB,OAAO,EAAE,iCAAiC;SAC3C,CAAC,CAAC;IACL,CAAC;IACD,IAAI,CAAC,8BAA8B,CAAC,MAAM,CAAC,EAAE,CAAC;QAC5C,GAAG,CAAC,QAAQ,CAAC;YACX,IAAI,EAAE,QAAQ;YACd,IAAI,EAAE,CAAC,kBAAkB,CAAC;YAC1B,OAAO,EAAE,mCAAmC;SAC7C,CAAC,CAAC;IACL,CAAC;AACH,CAAC;AAED,wGAAwG;AACxG,MAAM,CAAC,MAAM,wBAAwB,GAAG,WAAW,CAAC;AAEpD,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC;KAC/B,MAAM,CAAC;IACN,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE;IACvB,UAAU,EAAE,gBAAgB;IAC5B,UAAU,EAAE,gBAAgB;IAC5B,eAAe,EAAE,kBAAkB;IACnC,aAAa,EAAE,kBAAkB;IACjC,MAAM,EAAE,kBAAkB;IAC1B,8FAA8F;IAC9F,gGAAgG;IAChG,qCAAqC;IACrC,aAAa,EAAE,wBAAwB;IACvC,iBAAiB,EAAE,wBAAwB;IAC3C,gBAAgB,EAAE,wBAAwB;IAC1C,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE;IAC3B,gBAAgB,EAAE,sBAAsB,CAAC,QAAQ,EAAE;IACnD,2FAA2F;IAC3F,gEAAgE;IAChE,QAAQ,EAAE,oBAAoB,CAAC,QAAQ,EAAE;IACzC,0FAA0F;IAC1F,8EAA8E;IAC9E,mGAAmG;IACnG,kGAAkG;IAClG,YAAY,EAAE,WAAW,CAAC,QAAQ,EAAE;IACpC,6FAA6F;IAC7F,iGAAiG;IACjG,4CAA4C;IAC5C,YAAY,EAAE,WAAW,CAAC,QAAQ,EAAE;IACpC,SAAS,EAAE,aAAa;IACxB,SAAS,EAAE,aAAa;CACzB,CAAC;KACD,WAAW,CAAC,oBAAoB,CAAC,CAAC","sourcesContent":["// `sportLeagues/{leagueId}` — one league: its sport, mode, dates, status, playoff cutoffs, and waiver\n// requirement. The admin league callables write its settings, status, and mode; the picture core\n// writes `pictures`. Teams, games, and player stats live in their own collections and are never\n// copied onto the league.\n\nimport { z } from 'zod';\nimport {\n calendarDateSchema,\n countSchema,\n epochMsSchema,\n leagueModeSchema,\n leagueStatusSchema,\n leagueTypeSchema,\n} from './atoms.js';\nimport { GallerySummarySchema } from './gallery.js';\nimport { waiverTemplateIdSchema } from './waiver-template.js';\n\nexport const LEAGUE_DATES_OUT_OF_ORDER_MESSAGE = 'The league cannot end before it starts.';\nexport const WAIVER_REQUIREMENT_MISMATCH_MESSAGE =\n 'Choose a waiver template when the league requires a waiver, and none when it does not.';\n\n/**\n * A league names a waiver template exactly when it requires a waiver, so no reader needs a\n * sentinel for \"no waiver\" and no stale template id survives turning the requirement off.\n */\nexport function hasConsistentWaiverRequirement(league: {\n requiresWaiver: boolean;\n waiverTemplateId?: string | undefined;\n}): boolean {\n return league.requiresWaiver === (league.waiverTemplateId !== undefined);\n}\n\n/**\n * The cross-field rules every league settings shape obeys — the stored document and both callable\n * inputs — so the rule a form shows, the callable enforces, and the copy action validates is one\n * rule.\n */\nexport function refineLeagueSettings<\n T extends {\n leagueStartDate: string;\n leagueEndDate: string;\n requiresWaiver: boolean;\n waiverTemplateId?: string | undefined;\n },\n>(league: T, ctx: z.core.$RefinementCtx<T>): void {\n if (league.leagueEndDate < league.leagueStartDate) {\n ctx.addIssue({\n code: 'custom',\n path: ['leagueEndDate'],\n message: LEAGUE_DATES_OUT_OF_ORDER_MESSAGE,\n });\n }\n if (!hasConsistentWaiverRequirement(league)) {\n ctx.addIssue({\n code: 'custom',\n path: ['waiverTemplateId'],\n message: WAIVER_REQUIREMENT_MISMATCH_MESSAGE,\n });\n }\n}\n\n// How many teams reach the playoffs is a count; the callable inputs bound it with `MAX_PLAYOFF_CUTOFF`.\nexport const playoffCutoffCountSchema = countSchema;\n\nexport const SportLeagueSchema = z\n .object({\n leagueTitle: z.string(),\n description: z.string(),\n leagueType: leagueTypeSchema,\n leagueMode: leagueModeSchema,\n leagueStartDate: calendarDateSchema,\n leagueEndDate: calendarDateSchema,\n status: leagueStatusSchema,\n // How many teams reach the playoffs: `playoffCutoff` in a Regular league, the Competitive and\n // Recreational pools' own cutoffs in a Competitive/Recreational one. All three are kept in both\n // modes so a mode switch loses none.\n playoffCutoff: playoffCutoffCountSchema,\n playoffCutoffComp: playoffCutoffCountSchema,\n playoffCutoffRec: playoffCutoffCountSchema,\n requiresWaiver: z.boolean(),\n waiverTemplateId: waiverTemplateIdSchema.optional(),\n // The league's picture gallery summary — absent is an empty gallery. Only the picture core\n // writes it; the league callables and the copy action never do.\n pictures: GallerySummarySchema.optional(),\n // Advanced by every team created in the league; absent until the first. Only its changing\n // matters: team creation counts the league's teams in its transaction against\n // MAX_TEAMS_PER_LEAGUE, and a query does not lock against an insert — so each creation writes this\n // document, and two creations at the cap conflict instead of both passing (RULE-STD-BACKEND-116).\n teamRevision: countSchema.optional(),\n // Advanced by every game created in the league, a schedule week's included; absent until the\n // first. Only its changing matters, the same way: game creation counts the league's games in its\n // transaction against MAX_GAMES_PER_LEAGUE.\n gameRevision: countSchema.optional(),\n createdAt: epochMsSchema,\n updatedAt: epochMsSchema,\n })\n .superRefine(refineLeagueSettings);\nexport type SportLeague = z.infer<typeof SportLeagueSchema>;\n"]}
@@ -100,7 +100,7 @@ export const GhostLinkedGameSchema = z.strictObject({
100
100
  // One derived line a stats action moved — a team's standings line, a per-membership row, a lifetime
101
101
  // line, or a hall-of-fame entry — with its value before the action's first change to it and after
102
102
  // its latest, `null` where there was or is no line. Every repair's audit event records them, and a
103
- // dry run lists them without writing (BACKEND-212).
103
+ // dry run lists them without writing (RULE-STD-BACKEND-212).
104
104
  export const StatsDestinationChangeSchema = z.strictObject({
105
105
  ref: z.string().min(1),
106
106
  field: z.string().min(1),
@@ -110,7 +110,7 @@ export const StatsDestinationChangeSchema = z.strictObject({
110
110
  // The ghost's one operation records the account a link chose, or the removal, so a call naming a
111
111
  // different account — or a removal against a link — is refused, and the same call resumes. Neither
112
112
  // takes a reason: a link is chosen and confirmed, and its audit event records who linked which ghost
113
- // to whom and what moved (BACKEND-212).
113
+ // to whom and what moved (RULE-STD-BACKEND-212).
114
114
  export const GhostPlayerStatsOperationSchema = z
115
115
  .strictObject({
116
116
  ...operationBase,