@chaos-overlords/kernel 0.8.0 → 0.8.2

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 (52) hide show
  1. package/dist/domain/entities.d.ts +27 -1
  2. package/dist/domain/entities.d.ts.map +1 -1
  3. package/dist/domain/entities.js.map +1 -1
  4. package/dist/domain/errors.d.ts.map +1 -1
  5. package/dist/domain/errors.js.map +1 -1
  6. package/dist/logic/canonical-json.d.ts.map +1 -1
  7. package/dist/logic/canonical-json.js.map +1 -1
  8. package/dist/ports/storage.d.ts +130 -12
  9. package/dist/ports/storage.d.ts.map +1 -1
  10. package/dist/services/AuthService.d.ts.map +1 -1
  11. package/dist/services/AuthService.js.map +1 -1
  12. package/dist/services/EventPublisher.d.ts.map +1 -1
  13. package/dist/services/EventPublisher.js.map +1 -1
  14. package/dist/services/LobbyService.d.ts +32 -2
  15. package/dist/services/LobbyService.d.ts.map +1 -1
  16. package/dist/services/LobbyService.js +221 -44
  17. package/dist/services/LobbyService.js.map +1 -1
  18. package/dist/services/MatchQueryService.d.ts +8 -0
  19. package/dist/services/MatchQueryService.d.ts.map +1 -1
  20. package/dist/services/MatchQueryService.js +30 -19
  21. package/dist/services/MatchQueryService.js.map +1 -1
  22. package/dist/services/RateLimiter.d.ts +6 -0
  23. package/dist/services/RateLimiter.d.ts.map +1 -1
  24. package/dist/services/RateLimiter.js +39 -0
  25. package/dist/services/RateLimiter.js.map +1 -1
  26. package/dist/services/RetentionService.d.ts +13 -0
  27. package/dist/services/RetentionService.d.ts.map +1 -1
  28. package/dist/services/RetentionService.js +19 -2
  29. package/dist/services/RetentionService.js.map +1 -1
  30. package/dist/services/SnapshotService.d.ts +46 -9
  31. package/dist/services/SnapshotService.d.ts.map +1 -1
  32. package/dist/services/SnapshotService.js +122 -39
  33. package/dist/services/SnapshotService.js.map +1 -1
  34. package/dist/services/TurnService.d.ts +35 -3
  35. package/dist/services/TurnService.d.ts.map +1 -1
  36. package/dist/services/TurnService.js +205 -35
  37. package/dist/services/TurnService.js.map +1 -1
  38. package/dist/services/createKernel.js.map +1 -1
  39. package/dist/testing/CountingStorage.d.ts +25 -0
  40. package/dist/testing/CountingStorage.d.ts.map +1 -0
  41. package/dist/testing/CountingStorage.js +51 -0
  42. package/dist/testing/CountingStorage.js.map +1 -0
  43. package/dist/testing/InMemoryStorage.d.ts.map +1 -1
  44. package/dist/testing/InMemoryStorage.js +83 -17
  45. package/dist/testing/InMemoryStorage.js.map +1 -1
  46. package/dist/testing/fakes.d.ts.map +1 -1
  47. package/dist/testing/fakes.js.map +1 -1
  48. package/dist/testing/index.d.ts +1 -0
  49. package/dist/testing/index.d.ts.map +1 -1
  50. package/dist/testing/index.js +1 -0
  51. package/dist/testing/index.js.map +1 -1
  52. package/package.json +4 -4
@@ -1 +1 @@
1
- {"version":3,"file":"MatchQueryService.js","sourceRoot":"","sources":["../../src/services/MatchQueryService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"MatchQueryService.js","sourceRoot":"","sources":["../../src/services/MatchQueryService.ts"],"names":[],"mappings":"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"}
@@ -15,6 +15,10 @@ export declare class RateLimiter {
15
15
  });
16
16
  /** Returns the retry delay in seconds when over the limit, or null when the call is allowed. */
17
17
  take(key: string): number | null;
18
+ /** What `take` would answer right now, spending nothing. */
19
+ peek(key: string): number | null;
20
+ /** How much of `key`'s budget the current window has already spent; 0 once it has rolled. */
21
+ spent(key: string): number;
18
22
  /**
19
23
  * Drop windows that have expired, at most once per window period.
20
24
  *
@@ -24,5 +28,7 @@ export declare class RateLimiter {
24
28
  * entries can never outlive two windows.
25
29
  */
26
30
  private prune;
31
+ /** Hard bound on residency, for windows too long for `prune` to be the only collector. */
32
+ private evictOldest;
27
33
  }
28
34
  //# sourceMappingURL=RateLimiter.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RateLimiter.d.ts","sourceRoot":"","sources":["../../src/services/RateLimiter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAE7C;;;;GAIG;AACH,qBAAa,WAAW;IAKpB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4D;IACpF,OAAO,CAAC,SAAS,CAA2B;gBAGzB,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE;IAG/D,gGAAgG;IAChG,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI;IAehC;;;;;;;OAOG;IACH,OAAO,CAAC,KAAK;CAOd"}
1
+ {"version":3,"file":"RateLimiter.d.ts","sourceRoot":"","sources":["../../src/services/RateLimiter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,kBAAkB,CAAA;AAc7C;;;;GAIG;AACH,qBAAa,WAAW;IAKpB,OAAO,CAAC,QAAQ,CAAC,KAAK;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAL1B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA4D;IACpF,OAAO,CAAC,SAAS,CAA2B;IAE5C,YACmB,KAAK,EAAE,KAAK,EACZ,OAAO,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,EAC3D;IAEJ,gGAAgG;IAChG,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAc/B;IAED,4DAA4D;IAC5D,IAAI,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAM/B;IAED,6FAA6F;IAC7F,KAAK,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAKzB;IAED;;;;;;;OAOG;IACH,OAAO,CAAC,KAAK;IAQb,0FAA0F;IAC1F,OAAO,CAAC,WAAW;CAOpB"}
@@ -1,3 +1,14 @@
1
+ /**
2
+ * How many distinct keys one limiter holds before the oldest are dropped.
3
+ *
4
+ * `prune` only collects windows that have EXPIRED, at most once per window period, which is right
5
+ * for a minute-long window and wrong for a day-long one: the bug-report budget's window is
6
+ * twenty-four hours, so every address that ever posted stayed resident for a day or two on an
7
+ * unauthenticated route. A Map iterates in insertion order, so the head is the oldest key and
8
+ * evicting from there is one shift. Dropping a live window forgives that key's remaining budget,
9
+ * which is the safe direction to fail on a memory bound.
10
+ */
11
+ const MAX_KEYS = 50_000;
1
12
  /**
2
13
  * Fixed-window counter per key, in memory. It protects the unauthenticated doors (create, join)
3
14
  * from brute force on one process; a horizontally scaled deployment puts a shared limiter (a WAF
@@ -19,6 +30,7 @@ export class RateLimiter {
19
30
  if (!entry || now - entry.windowStart >= this.options.windowMs) {
20
31
  this.windows.set(key, { windowStart: now, count: 1 });
21
32
  this.prune(now);
33
+ this.evictOldest();
22
34
  return null;
23
35
  }
24
36
  if (entry.count >= this.options.limit) {
@@ -27,6 +39,24 @@ export class RateLimiter {
27
39
  entry.count += 1;
28
40
  return null;
29
41
  }
42
+ /** What `take` would answer right now, spending nothing. */
43
+ peek(key) {
44
+ const now = this.clock.now().getTime();
45
+ const entry = this.windows.get(key);
46
+ if (!entry || now - entry.windowStart >= this.options.windowMs)
47
+ return null;
48
+ if (entry.count < this.options.limit)
49
+ return null;
50
+ return Math.ceil((entry.windowStart + this.options.windowMs - now) / 1000);
51
+ }
52
+ /** How much of `key`'s budget the current window has already spent; 0 once it has rolled. */
53
+ spent(key) {
54
+ const now = this.clock.now().getTime();
55
+ const entry = this.windows.get(key);
56
+ if (!entry || now - entry.windowStart >= this.options.windowMs)
57
+ return 0;
58
+ return entry.count;
59
+ }
30
60
  /**
31
61
  * Drop windows that have expired, at most once per window period.
32
62
  *
@@ -44,5 +74,14 @@ export class RateLimiter {
44
74
  this.windows.delete(key);
45
75
  }
46
76
  }
77
+ /** Hard bound on residency, for windows too long for `prune` to be the only collector. */
78
+ evictOldest() {
79
+ while (this.windows.size > MAX_KEYS) {
80
+ const oldest = this.windows.keys().next();
81
+ if (oldest.done)
82
+ return;
83
+ this.windows.delete(oldest.value);
84
+ }
85
+ }
47
86
  }
48
87
  //# sourceMappingURL=RateLimiter.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"RateLimiter.js","sourceRoot":"","sources":["../../src/services/RateLimiter.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,OAAO,WAAW;IAKH;IACA;IALF,OAAO,GAAG,IAAI,GAAG,EAAkD,CAAA;IAC5E,SAAS,GAAG,MAAM,CAAC,iBAAiB,CAAA;IAE5C,YACmB,KAAY,EACZ,OAA4C;QAD5C,UAAK,GAAL,KAAK,CAAO;QACZ,YAAO,GAAP,OAAO,CAAqC;IAC5D,CAAC;IAEJ,gGAAgG;IAChG,IAAI,CAAC,GAAW;QACd,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAA;QACtC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACnC,IAAI,CAAC,KAAK,IAAI,GAAG,GAAG,KAAK,CAAC,WAAW,IAAI,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;YAC/D,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,WAAW,EAAE,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAA;YACrD,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;YACf,OAAO,IAAI,CAAA;QACb,CAAC;QACD,IAAI,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACtC,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,GAAG,GAAG,CAAC,GAAG,IAAI,CAAC,CAAA;QAC5E,CAAC;QACD,KAAK,CAAC,KAAK,IAAI,CAAC,CAAA;QAChB,OAAO,IAAI,CAAA;IACb,CAAC;IAED;;;;;;;OAOG;IACK,KAAK,CAAC,GAAW;QACvB,IAAI,GAAG,GAAG,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ;YAAE,OAAM;QACxD,IAAI,CAAC,SAAS,GAAG,GAAG,CAAA;QACpB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YACxC,IAAI,GAAG,GAAG,KAAK,CAAC,WAAW,IAAI,IAAI,CAAC,OAAO,CAAC,QAAQ;gBAAE,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAChF,CAAC;IACH,CAAC;CACF"}
1
+ {"version":3,"file":"RateLimiter.js","sourceRoot":"","sources":["../../src/services/RateLimiter.ts"],"names":[],"mappings":"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"}
@@ -12,6 +12,17 @@ export interface RetentionPolicy {
12
12
  * the ordinary end of a match on a public server, and nothing else ever collected it.
13
13
  */
14
14
  abandonedLiveMaxAgeMs: number;
15
+ /**
16
+ * Age after which a running or desynced match is deleted whatever its roster says. 0 keeps them
17
+ * forever.
18
+ *
19
+ * The roster test alone is not enough to collect anything. A player stops being `active` through
20
+ * `leave`, `kick` or a missed deadline, and the game's default is an untimed match with no
21
+ * deadline to miss, so two friends whose clients both died leave two `active` rows that nothing
22
+ * ever clears. That is the ordinary end of an untimed match, and without this window those
23
+ * matches, their orders, their events and up to five megabytes of snapshot are immortal.
24
+ */
25
+ silentLiveMaxAgeMs: number;
15
26
  /** Matches deleted per sweep, so one pass cannot monopolise the database. */
16
27
  batchSize: number;
17
28
  }
@@ -33,5 +44,7 @@ export declare class RetentionService {
33
44
  collect(): Promise<number>;
34
45
  private collectTerminated;
35
46
  private collectAbandonedLive;
47
+ /** The same delete without the roster test, on a window long enough to stand in for it. */
48
+ private collectSilentLive;
36
49
  }
37
50
  //# sourceMappingURL=RetentionService.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"RetentionService.d.ts","sourceRoot":"","sources":["../../src/services/RetentionService.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AAKxC,MAAM,WAAW,eAAe;IAC9B,mGAAmG;IACnG,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;;;;;OAQG;IACH,qBAAqB,EAAE,MAAM,CAAA;IAC7B,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAA;CAClB;AAID,eAAO,MAAM,iBAAiB,EAAE,eAI/B,CAAA;AAED;;;;;;;;;GASG;AACH,qBAAa,gBAAgB;IAEzB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC,EACtD,MAAM,GAAE,eAAmC;IAGxD,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC;YAIlB,iBAAiB;YAiBjB,oBAAoB;CAenC"}
1
+ {"version":3,"file":"RetentionService.d.ts","sourceRoot":"","sources":["../../src/services/RetentionService.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AAKxC,MAAM,WAAW,eAAe;IAC9B,mGAAmG;IACnG,QAAQ,EAAE,MAAM,CAAA;IAChB;;;;;;;;OAQG;IACH,qBAAqB,EAAE,MAAM,CAAA;IAC7B;;;;;;;;;OASG;IACH,kBAAkB,EAAE,MAAM,CAAA;IAC1B,6EAA6E;IAC7E,SAAS,EAAE,MAAM,CAAA;CAClB;AAID,eAAO,MAAM,iBAAiB,EAAE,eAK/B,CAAA;AAED;;;;;;;;;GASG;AACH,qBAAa,gBAAgB;IAEzB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM;IAFzB,YACmB,IAAI,EAAE,IAAI,CAAC,UAAU,EAAE,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC,EACtD,MAAM,GAAE,eAAmC,EAC1D;IAEE,OAAO,IAAI,OAAO,CAAC,MAAM,CAAC,CAM/B;YAEa,iBAAiB;YAiBjB,oBAAoB;IAiBlC,2FAA2F;YAC7E,iBAAiB;CAgBhC"}
@@ -4,6 +4,7 @@ const DAY_MS = 24 * 60 * 60 * 1000;
4
4
  export const DEFAULT_RETENTION = {
5
5
  maxAgeMs: 30 * DAY_MS,
6
6
  abandonedLiveMaxAgeMs: 90 * DAY_MS,
7
+ silentLiveMaxAgeMs: 180 * DAY_MS,
7
8
  batchSize: 50,
8
9
  };
9
10
  /**
@@ -24,7 +25,9 @@ export class RetentionService {
24
25
  this.policy = policy;
25
26
  }
26
27
  async collect() {
27
- return (await this.collectTerminated()) + (await this.collectAbandonedLive());
28
+ return ((await this.collectTerminated()) +
29
+ (await this.collectAbandonedLive()) +
30
+ (await this.collectSilentLive()));
28
31
  }
29
32
  async collectTerminated() {
30
33
  if (this.policy.maxAgeMs <= 0)
@@ -43,7 +46,7 @@ export class RetentionService {
43
46
  if (this.policy.abandonedLiveMaxAgeMs <= 0)
44
47
  return 0;
45
48
  const before = new Date(this.deps.clock.now().getTime() - this.policy.abandonedLiveMaxAgeMs);
46
- const deleted = await this.deps.storage.matches.deleteAbandonedLive(before, this.policy.batchSize);
49
+ const deleted = await this.deps.storage.matches.deleteAbandonedLive(before, this.policy.batchSize, true);
47
50
  if (deleted > 0) {
48
51
  this.deps.logger.info('retention deleted long-abandoned live matches', {
49
52
  deleted,
@@ -52,5 +55,19 @@ export class RetentionService {
52
55
  }
53
56
  return deleted;
54
57
  }
58
+ /** The same delete without the roster test, on a window long enough to stand in for it. */
59
+ async collectSilentLive() {
60
+ if (this.policy.silentLiveMaxAgeMs <= 0)
61
+ return 0;
62
+ const before = new Date(this.deps.clock.now().getTime() - this.policy.silentLiveMaxAgeMs);
63
+ const deleted = await this.deps.storage.matches.deleteAbandonedLive(before, this.policy.batchSize, false);
64
+ if (deleted > 0) {
65
+ this.deps.logger.info('retention deleted silent live matches', {
66
+ deleted,
67
+ before: before.toISOString(),
68
+ });
69
+ }
70
+ return deleted;
71
+ }
55
72
  }
56
73
  //# sourceMappingURL=RetentionService.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"RetentionService.js","sourceRoot":"","sources":["../../src/services/RetentionService.ts"],"names":[],"mappings":"AAGA,qGAAqG;AACrG,MAAM,WAAW,GAA2B,CAAC,UAAU,EAAE,WAAW,EAAE,OAAO,CAAC,CAAA;AAmB9E,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AAElC,MAAM,CAAC,MAAM,iBAAiB,GAAoB;IAChD,QAAQ,EAAE,EAAE,GAAG,MAAM;IACrB,qBAAqB,EAAE,EAAE,GAAG,MAAM;IAClC,SAAS,EAAE,EAAE;CACd,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,OAAO,gBAAgB;IAER;IACA;IAFnB,YACmB,IAAsD,EACtD,SAA0B,iBAAiB;QAD3C,SAAI,GAAJ,IAAI,CAAkD;QACtD,WAAM,GAAN,MAAM,CAAqC;IAC3D,CAAC;IAEJ,KAAK,CAAC,OAAO;QACX,OAAO,CAAC,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC,GAAG,CAAC,MAAM,IAAI,CAAC,oBAAoB,EAAE,CAAC,CAAA;IAC/E,CAAC;IAEO,KAAK,CAAC,iBAAiB;QAC7B,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC;YAAE,OAAO,CAAC,CAAA;QACvC,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QAC/E,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,cAAc,CAC5D,WAAW,EACX,MAAM,EACN,IAAI,CAAC,MAAM,CAAC,SAAS,CACtB,CAAA;QACD,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YAChB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,oCAAoC,EAAE;gBAC1D,OAAO;gBACP,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE;aAC7B,CAAC,CAAA;QACJ,CAAC;QACD,OAAO,OAAO,CAAA;IAChB,CAAC;IAEO,KAAK,CAAC,oBAAoB;QAChC,IAAI,IAAI,CAAC,MAAM,CAAC,qBAAqB,IAAI,CAAC;YAAE,OAAO,CAAC,CAAA;QACpD,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAA;QAC5F,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CACjE,MAAM,EACN,IAAI,CAAC,MAAM,CAAC,SAAS,CACtB,CAAA;QACD,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YAChB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,+CAA+C,EAAE;gBACrE,OAAO;gBACP,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE;aAC7B,CAAC,CAAA;QACJ,CAAC;QACD,OAAO,OAAO,CAAA;IAChB,CAAC;CACF"}
1
+ {"version":3,"file":"RetentionService.js","sourceRoot":"","sources":["../../src/services/RetentionService.ts"],"names":[],"mappings":"AAGA,qGAAqG;AACrG,MAAM,WAAW,GAA2B,CAAC,UAAU,EAAE,WAAW,EAAE,OAAO,CAAC,CAAA;AA8B9E,MAAM,MAAM,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAA;AAElC,MAAM,CAAC,MAAM,iBAAiB,GAAoB;IAChD,QAAQ,EAAE,EAAE,GAAG,MAAM;IACrB,qBAAqB,EAAE,EAAE,GAAG,MAAM;IAClC,kBAAkB,EAAE,GAAG,GAAG,MAAM;IAChC,SAAS,EAAE,EAAE;CACd,CAAA;AAED;;;;;;;;;GASG;AACH,MAAM,OAAO,gBAAgB;IAER,IAAI;IACJ,MAAM;IAFzB,YACmB,IAAsD,EACtD,MAAM,GAAoB,iBAAiB;oBAD3C,IAAI;sBACJ,MAAM;IACtB,CAAC;IAEJ,KAAK,CAAC,OAAO;QACX,OAAO,CACL,CAAC,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC;YAChC,CAAC,MAAM,IAAI,CAAC,oBAAoB,EAAE,CAAC;YACnC,CAAC,MAAM,IAAI,CAAC,iBAAiB,EAAE,CAAC,CACjC,CAAA;IACH,CAAC;IAEO,KAAK,CAAC,iBAAiB;QAC7B,IAAI,IAAI,CAAC,MAAM,CAAC,QAAQ,IAAI,CAAC;YAAE,OAAO,CAAC,CAAA;QACvC,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAA;QAC/E,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,cAAc,CAC5D,WAAW,EACX,MAAM,EACN,IAAI,CAAC,MAAM,CAAC,SAAS,CACtB,CAAA;QACD,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YAChB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,oCAAoC,EAAE;gBAC1D,OAAO;gBACP,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE;aAC7B,CAAC,CAAA;QACJ,CAAC;QACD,OAAO,OAAO,CAAA;IAChB,CAAC;IAEO,KAAK,CAAC,oBAAoB;QAChC,IAAI,IAAI,CAAC,MAAM,CAAC,qBAAqB,IAAI,CAAC;YAAE,OAAO,CAAC,CAAA;QACpD,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,qBAAqB,CAAC,CAAA;QAC5F,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CACjE,MAAM,EACN,IAAI,CAAC,MAAM,CAAC,SAAS,EACrB,IAAI,CACL,CAAA;QACD,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YAChB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,+CAA+C,EAAE;gBACrE,OAAO;gBACP,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE;aAC7B,CAAC,CAAA;QACJ,CAAC;QACD,OAAO,OAAO,CAAA;IAChB,CAAC;IAED,2FAA2F;IACnF,KAAK,CAAC,iBAAiB;QAC7B,IAAI,IAAI,CAAC,MAAM,CAAC,kBAAkB,IAAI,CAAC;YAAE,OAAO,CAAC,CAAA;QACjD,MAAM,MAAM,GAAG,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,MAAM,CAAC,kBAAkB,CAAC,CAAA;QACzF,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CACjE,MAAM,EACN,IAAI,CAAC,MAAM,CAAC,SAAS,EACrB,KAAK,CACN,CAAA;QACD,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YAChB,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,uCAAuC,EAAE;gBAC7D,OAAO;gBACP,MAAM,EAAE,MAAM,CAAC,WAAW,EAAE;aAC7B,CAAC,CAAA;QACJ,CAAC;QACD,OAAO,OAAO,CAAA;IAChB,CAAC;CACF"}
@@ -14,23 +14,60 @@ export declare class SnapshotService {
14
14
  private readonly turns;
15
15
  constructor(deps: KernelDeps, publisher: EventPublisher, turns: TurnService);
16
16
  upload(principal: Principal, request: UploadSnapshotRequest): Promise<void>;
17
+ /**
18
+ * A periodic checkpoint of a turn the match has already agreed on.
19
+ *
20
+ * Only two kinds of snapshot used to exist, the host's turn-0 bootstrap and desync repairs, so a
21
+ * client reconnecting at turn 80 replayed eighty turns from the bootstrap — eighty sequential
22
+ * fetches, each under its own deadline, and eighty full turn resolutions, before the player saw
23
+ * anything. The design doc talks about matches of hundreds of turns.
24
+ *
25
+ * It is not a second way to say what a turn's state was. The turn is CONFIRMED, so the verdict
26
+ * has already been reached and written on the turn row, and the checkpoint is refused unless it
27
+ * claims exactly that hash: it can only agree with what every client already computed. The host
28
+ * writes it because the host is the one client the protocol already asks for bytes, storage stays
29
+ * bounded by the per-match snapshot pruning, and the rate stays bounded by the upload budget.
30
+ *
31
+ * Nothing is announced. A checkpoint changes nothing about the match; it is there for whoever
32
+ * reconnects next, and `getLatest` is what finds it.
33
+ */
34
+ private storeCheckpoint;
35
+ private snapshotOf;
17
36
  /**
18
37
  * Late-join hints are optional metadata; the snapshot the players are waiting on is not. A
19
38
  * transient failure of this write is logged and never fails the upload that already succeeded.
20
39
  */
21
40
  private publishSeatSummaries;
22
41
  /**
23
- * A recovery snapshot may only claim a state hash that the players themselves already computed
24
- * in the greatest number.
42
+ * A repair must name the turn that actually diverged.
43
+ *
44
+ * "Below `currentTurn` while the match is desynced" was too loose, and the corroboration check
45
+ * below cannot make up the difference: it counts reports, and a turn with none — the successor a
46
+ * seal opened before anybody had reported on it — has no candidates to contradict, so it returned
47
+ * early and let the host store any hash it liked for that turn. `evaluateConsensus` then had an
48
+ * authoritative hash to judge against and could only confirm or wait, never desync. That is
49
+ * exactly the "host arbitrates a disagreement it is a party to" case the corroboration rule
50
+ * exists to prevent; it needs the turn's own status to close it.
51
+ */
52
+ private requireDesyncedTurn;
53
+ /**
54
+ * Who may post a repair, and which hash they may claim.
55
+ *
56
+ * The hash is the hard rule and it binds everyone: the snapshot for a desynced turn becomes the
57
+ * state every other client is told to converge on, so a client free to name any hash could
58
+ * resolve a deliberate desync in favour of a doctored one. A hash that more active players
59
+ * reported than any other cannot be minted by one of them.
25
60
  *
26
- * This is what keeps the host from arbitrating a disagreement it is a party to. The snapshot for
27
- * a desynced turn becomes the hash every other client is told to converge on, so a host free to
28
- * name any hash could resolve a deliberate desync in favour of a doctored state. A hash that more
29
- * active players reported than any other cannot be minted by one of them. A tie leaves nothing to
30
- * count and the host breaks it; turns with no reports at all (a bootstrap snapshot for a
31
- * reconnecting client) are unconstrained, because there is no consensus to contradict yet.
61
+ * Who may post it follows from that. A hash that is the SOLE most-reported one is already the
62
+ * majority's, so whoever holds it may post the repair whether or not they are the host. That is
63
+ * the one case a host-only rule could not serve at all: a host that is itself the odd one out can
64
+ * never name a majority hash, so no repair existed and the match stayed paused until retention
65
+ * collected it. A TIE — most of all the 1-1 split of a two-player match — is different: there is
66
+ * no majority, nothing to count, and no third party to ask, so the host breaks it as before and a
67
+ * peer may not. Letting either side of a tie impose its state would hand a two-player match to
68
+ * whoever uploaded first.
32
69
  */
33
- private requireCorroboration;
70
+ private requireRepairAuthority;
34
71
  /**
35
72
  * Bound what one live match holds. A snapshot is a megabyte of base64 and a long match can desync
36
73
  * many times; retention only collects matches that are over, so without this a single match that
@@ -1 +1 @@
1
- {"version":3,"file":"SnapshotService.d.ts","sourceRoot":"","sources":["../../src/services/SnapshotService.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EAEjB,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAC3B,MAAM,4BAA4B,CAAA;AAKnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAQhD;;;;GAIG;AACH,qBAAa,eAAe;IAExB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAFL,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,cAAc,EACzB,KAAK,EAAE,WAAW;IAG/B,MAAM,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAuDjF;;;OAGG;YACW,oBAAoB;IAYlC;;;;;;;;;;OAUG;YACW,oBAAoB;IAiBlC;;;;;;OAMG;YACW,iBAAiB;IASzB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IAM9C,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;CAKhE;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,YAAY,EAC1B,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,GACpD,YAAY,CASd"}
1
+ {"version":3,"file":"SnapshotService.d.ts","sourceRoot":"","sources":["../../src/services/SnapshotService.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EAEjB,KAAK,YAAY,EACjB,KAAK,qBAAqB,EAC3B,MAAM,4BAA4B,CAAA;AAKnC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,QAAQ,CAAA;AACxC,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAA;AACtD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAA;AAQhD;;;;GAIG;AACH,qBAAa,eAAe;IAExB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,SAAS;IAC1B,OAAO,CAAC,QAAQ,CAAC,KAAK;IAHxB,YACmB,IAAI,EAAE,UAAU,EAChB,SAAS,EAAE,cAAc,EACzB,KAAK,EAAE,WAAW,EACjC;IAEE,MAAM,CAAC,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CA4DhF;IAED;;;;;;;;;;;;;;;;OAgBG;YACW,eAAe;YAqBf,UAAU;IAqBxB;;;OAGG;YACW,oBAAoB;IAYlC;;;;;;;;;;OAUG;IACH,OAAO,CAAC,mBAAmB;IAO3B;;;;;;;;;;;;;;;;OAgBG;YACW,sBAAsB;IAwBpC;;;;;;OAMG;YACW,iBAAiB;IASzB,MAAM,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAInD;IAEK,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAI9D;CACF;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,YAAY,EAC1B,aAAa,EAAE,qBAAqB,CAAC,eAAe,CAAC,GACpD,YAAY,CASd"}
@@ -23,15 +23,20 @@ export class SnapshotService {
23
23
  }
24
24
  async upload(principal, request) {
25
25
  const { match, player } = principal;
26
- if (player.id !== match.hostPlayerId) {
27
- throw new ForbiddenError('Only the host uploads snapshots', { reason: 'host_only' });
28
- }
29
26
  const isBootstrap = request.turn === 0 && match.status === 'running' && match.currentTurn === 1;
30
- if (!isBootstrap && match.status !== 'desynced') {
31
- throw new ConflictError('Full snapshots are only accepted for bootstrap or desync repair', {
32
- reason: 'snapshot_not_required',
27
+ // The bootstrap snapshot is the host's alone: it is unconstrained (nothing has been reported
28
+ // yet) and it is the match's starting state, which only the host has.
29
+ if (isBootstrap && player.id !== match.hostPlayerId) {
30
+ throw new ForbiddenError('Only the host uploads the starting snapshot', {
31
+ reason: 'host_only',
33
32
  });
34
33
  }
34
+ if (player.status !== 'active' && player.status !== 'takeoverPending') {
35
+ throw new ForbiddenError('You are no longer part of this match', { reason: 'not_active' });
36
+ }
37
+ if (!isBootstrap && match.status !== 'running' && match.status !== 'desynced') {
38
+ throw new ConflictError('The match is not in progress', { reason: 'match_not_running' });
39
+ }
35
40
  if (request.turn > match.currentTurn) {
36
41
  throw new ConflictError('Cannot snapshot a turn that has not opened', {
37
42
  reason: 'future_turn',
@@ -42,38 +47,86 @@ export class SnapshotService {
42
47
  reason: 'turn_open',
43
48
  });
44
49
  }
45
- // Bootstrap has no reports to corroborate. A repair does: only a hash the active players
46
- // reported most often can become the state every client is asked to adopt.
47
- await this.requireCorroboration(match.id, request.turn, request.stateHash);
50
+ if (isBootstrap) {
51
+ // Nothing has been reported yet, so there is no consensus a bootstrap could contradict.
52
+ await this.deps.storage.snapshots.put(await this.snapshotOf(match, player.id, request));
53
+ await this.publishSeatSummaries(match.id, mergeSeatSummaries(match.settings.gameSettings, request.seatSummaries));
54
+ await this.pruneOldSnapshots(match.id);
55
+ return;
56
+ }
57
+ const turn = await this.deps.storage.turns.get(match.id, request.turn);
58
+ if (!turn)
59
+ throw new NotFoundError('No such turn', { reason: 'unknown_turn' });
60
+ if (turn.status === 'confirmed') {
61
+ await this.storeCheckpoint(match, player, turn, request);
62
+ return;
63
+ }
64
+ this.requireDesyncedTurn(turn);
65
+ await this.requireRepairAuthority(match, player, request.turn, request.stateHash);
48
66
  // Judged before anything is written: a refusal after the row landed would leave a stored
49
67
  // snapshot the caller was told was rejected, with no `snapshot.available` and no verdict.
50
68
  const gameSettings = mergeSeatSummaries(match.settings.gameSettings, request.seatSummaries);
51
- const snapshot = {
69
+ await this.deps.storage.snapshots.put(await this.snapshotOf(match, player.id, request));
70
+ await this.publishSeatSummaries(match.id, gameSettings);
71
+ await this.pruneOldSnapshots(match.id);
72
+ await this.publisher.publish(match.id, {
73
+ type: 'snapshot.available',
74
+ payload: {
75
+ turn: request.turn,
76
+ formatVersion: request.formatVersion,
77
+ stateHash: request.stateHash,
78
+ uploadedByPlayerId: player.id,
79
+ },
80
+ });
81
+ await this.turns.settle(match.id, request.turn);
82
+ }
83
+ /**
84
+ * A periodic checkpoint of a turn the match has already agreed on.
85
+ *
86
+ * Only two kinds of snapshot used to exist, the host's turn-0 bootstrap and desync repairs, so a
87
+ * client reconnecting at turn 80 replayed eighty turns from the bootstrap — eighty sequential
88
+ * fetches, each under its own deadline, and eighty full turn resolutions, before the player saw
89
+ * anything. The design doc talks about matches of hundreds of turns.
90
+ *
91
+ * It is not a second way to say what a turn's state was. The turn is CONFIRMED, so the verdict
92
+ * has already been reached and written on the turn row, and the checkpoint is refused unless it
93
+ * claims exactly that hash: it can only agree with what every client already computed. The host
94
+ * writes it because the host is the one client the protocol already asks for bytes, storage stays
95
+ * bounded by the per-match snapshot pruning, and the rate stays bounded by the upload budget.
96
+ *
97
+ * Nothing is announced. A checkpoint changes nothing about the match; it is there for whoever
98
+ * reconnects next, and `getLatest` is what finds it.
99
+ */
100
+ async storeCheckpoint(match, player, turn, request) {
101
+ if (player.id !== match.hostPlayerId) {
102
+ throw new ForbiddenError('Only the host uploads checkpoints', { reason: 'host_only' });
103
+ }
104
+ if (turn.stateHash !== request.stateHash) {
105
+ throw new ConflictError('A checkpoint must be the state the turn was confirmed on', {
106
+ reason: 'uncorroborated_state_hash',
107
+ candidateStateHashes: turn.stateHash === null ? [] : [turn.stateHash],
108
+ });
109
+ }
110
+ const gameSettings = mergeSeatSummaries(match.settings.gameSettings, request.seatSummaries);
111
+ await this.deps.storage.snapshots.put(await this.snapshotOf(match, player.id, request));
112
+ await this.publishSeatSummaries(match.id, gameSettings);
113
+ await this.pruneOldSnapshots(match.id);
114
+ }
115
+ async snapshotOf(match, uploadedByPlayerId, request) {
116
+ // Re-running the settings schema over the merge is what holds the blob to its cap; it throws
117
+ // before anything is stored, which is why it is called on both paths before the write.
118
+ mergeSeatSummaries(match.settings.gameSettings, request.seatSummaries);
119
+ return {
52
120
  matchId: match.id,
53
121
  turn: request.turn,
54
122
  formatVersion: request.formatVersion,
55
123
  protocolVersion: request.protocolVersion ?? 1,
56
124
  sessionVersion: request.sessionVersion ?? 1,
57
125
  stateHash: request.stateHash,
58
- uploadedByPlayerId: player.id,
126
+ uploadedByPlayerId,
59
127
  uploadedAt: this.deps.clock.now(),
60
128
  body: request.body,
61
129
  };
62
- await this.deps.storage.snapshots.put(snapshot);
63
- await this.publishSeatSummaries(match.id, gameSettings);
64
- await this.pruneOldSnapshots(match.id);
65
- if (match.status === 'desynced') {
66
- await this.publisher.publish(match.id, {
67
- type: 'snapshot.available',
68
- payload: {
69
- turn: request.turn,
70
- formatVersion: request.formatVersion,
71
- stateHash: request.stateHash,
72
- uploadedByPlayerId: player.id,
73
- },
74
- });
75
- await this.turns.settle(match.id, request.turn);
76
- }
77
130
  }
78
131
  /**
79
132
  * Late-join hints are optional metadata; the snapshot the players are waiting on is not. A
@@ -88,25 +141,55 @@ export class SnapshotService {
88
141
  }
89
142
  }
90
143
  /**
91
- * A recovery snapshot may only claim a state hash that the players themselves already computed
92
- * in the greatest number.
144
+ * A repair must name the turn that actually diverged.
93
145
  *
94
- * This is what keeps the host from arbitrating a disagreement it is a party to. The snapshot for
95
- * a desynced turn becomes the hash every other client is told to converge on, so a host free to
96
- * name any hash could resolve a deliberate desync in favour of a doctored state. A hash that more
97
- * active players reported than any other cannot be minted by one of them. A tie leaves nothing to
98
- * count and the host breaks it; turns with no reports at all (a bootstrap snapshot for a
99
- * reconnecting client) are unconstrained, because there is no consensus to contradict yet.
146
+ * "Below `currentTurn` while the match is desynced" was too loose, and the corroboration check
147
+ * below cannot make up the difference: it counts reports, and a turn with none — the successor a
148
+ * seal opened before anybody had reported on it — has no candidates to contradict, so it returned
149
+ * early and let the host store any hash it liked for that turn. `evaluateConsensus` then had an
150
+ * authoritative hash to judge against and could only confirm or wait, never desync. That is
151
+ * exactly the "host arbitrates a disagreement it is a party to" case the corroboration rule
152
+ * exists to prevent; it needs the turn's own status to close it.
100
153
  */
101
- async requireCorroboration(matchId, turn, stateHash) {
154
+ requireDesyncedTurn(turn) {
155
+ if (turn.status === 'desynced')
156
+ return;
157
+ throw new ConflictError('Only a turn that diverged may be repaired with a snapshot', {
158
+ reason: 'turn_not_desynced',
159
+ });
160
+ }
161
+ /**
162
+ * Who may post a repair, and which hash they may claim.
163
+ *
164
+ * The hash is the hard rule and it binds everyone: the snapshot for a desynced turn becomes the
165
+ * state every other client is told to converge on, so a client free to name any hash could
166
+ * resolve a deliberate desync in favour of a doctored one. A hash that more active players
167
+ * reported than any other cannot be minted by one of them.
168
+ *
169
+ * Who may post it follows from that. A hash that is the SOLE most-reported one is already the
170
+ * majority's, so whoever holds it may post the repair whether or not they are the host. That is
171
+ * the one case a host-only rule could not serve at all: a host that is itself the odd one out can
172
+ * never name a majority hash, so no repair existed and the match stayed paused until retention
173
+ * collected it. A TIE — most of all the 1-1 split of a two-player match — is different: there is
174
+ * no majority, nothing to count, and no third party to ask, so the host breaks it as before and a
175
+ * peer may not. Letting either side of a tie impose its state would hand a two-player match to
176
+ * whoever uploaded first.
177
+ */
178
+ async requireRepairAuthority(match, player, turn, stateHash) {
102
179
  const [players, reports] = await Promise.all([
103
- this.deps.storage.players.listByMatch(matchId),
104
- this.deps.storage.turns.listReports(matchId, turn),
180
+ this.deps.storage.players.listByMatch(match.id),
181
+ this.deps.storage.turns.listReports(match.id, turn),
105
182
  ]);
106
183
  const candidates = authoritativeCandidates(players, reports);
107
- if (candidates.length === 0 || candidates.includes(stateHash))
184
+ if (!candidates.includes(stateHash)) {
185
+ throw new ConflictError('A recovery snapshot must match a state hash the players reported most often', { reason: 'uncorroborated_state_hash', candidateStateHashes: candidates });
186
+ }
187
+ if (candidates.length === 1 || player.id === match.hostPlayerId)
108
188
  return;
109
- throw new ConflictError('A recovery snapshot must match a state hash the players reported most often', { reason: 'uncorroborated_state_hash', candidateStateHashes: candidates });
189
+ throw new ForbiddenError('Only the host may break a tie between reported states', {
190
+ reason: 'host_only',
191
+ candidateStateHashes: candidates,
192
+ });
110
193
  }
111
194
  /**
112
195
  * Bound what one live match holds. A snapshot is a megabyte of base64 and a long match can desync
@@ -1 +1 @@
1
- {"version":3,"file":"SnapshotService.js","sourceRoot":"","sources":["../../src/services/SnapshotService.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"SnapshotService.js","sourceRoot":"","sources":["../../src/services/SnapshotService.ts"],"names":[],"mappings":"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"}
@@ -20,6 +20,8 @@ export declare const FIRST_TURN = 1;
20
20
  export declare class TurnService {
21
21
  private readonly deps;
22
22
  private readonly publisher;
23
+ /** Passes since the last unbounded scan; see `sweep`. Starts due so the first pass is a full one. */
24
+ private passesSinceFullScan;
23
25
  constructor(deps: KernelDeps, publisher: EventPublisher);
24
26
  submitOrders(principal: Principal, number: number, request: SubmitOrdersRequest): Promise<OwnSubmissionView>;
25
27
  /** Seal `number` if its trigger condition holds. Returns whether THIS call sealed it. */
@@ -39,7 +41,14 @@ export declare class TurnService {
39
41
  * the announcement only when the log does not already carry it.
40
42
  */
41
43
  private repairInterruptedStart;
42
- /** Whether the log carries an event of `type`. Only read on repair paths, where the log is short. */
44
+ /**
45
+ * Whether the log carries an event of `type`.
46
+ *
47
+ * Only the interrupted-start repair asks this, and only about turn 1, so the log it pages is a
48
+ * handful of rows. The verdict used to ask it about `turn.desynced` on every sweep of a paused
49
+ * match, where the log is the whole match; that answer lives on the turn row now
50
+ * (`claimDesyncAnnouncement`), and this must not grow another steady-state caller.
51
+ */
43
52
  private hasEvent;
44
53
  /**
45
54
  * Opens turn `number` for every active player and arms its deadline. Idempotent: the insert is
@@ -56,6 +65,7 @@ export declare class TurnService {
56
65
  sealed: number;
57
66
  repaired: number;
58
67
  }>;
68
+ private guard;
59
69
  report(principal: Principal, number: number, request: TurnReportRequest): Promise<void>;
60
70
  /** Authenticated turn activity wins the race with an AI vote and restores the human seat. */
61
71
  private restorePendingPlayer;
@@ -64,10 +74,32 @@ export declare class TurnService {
64
74
  * (see `TakeoverRepository`) and announced exactly once: a second request for a seat whose
65
75
  * prompt is already open changes nothing, so a repeated seal step or a rejoin that re-asks about
66
76
  * every absent seat cannot double the modal on anyone's screen.
77
+ *
78
+ * Opening a prompt always stops the open turn's clock, because the decision owns the screen on
79
+ * every remaining client and time spent behind that modal is time nobody can plan in. The pause
80
+ * belongs here rather than at the call sites: a prompt raised by a rejoin re-asking about the
81
+ * seats it finds absent, or by a vote on a seat that went quiet before anyone was present to ask,
82
+ * is exactly as blocking as one raised by a kick, and each of those used to leave the countdown
83
+ * running. `openTurn` applies the same rule to a turn opening while a prompt is already up.
67
84
  */
68
85
  openTakeoverPrompt(matchId: string, playerId: string, turn: number): Promise<boolean>;
69
- /** Pause the open turn while players decide what to do with an absent human seat. */
70
- pauseForTakeoverVote(matchId: string): Promise<void>;
86
+ /**
87
+ * Pause the open turn while players decide what to do with an absent human seat.
88
+ *
89
+ * Only `openTakeoverPrompt` calls this, so every prompt pauses and none has to remember to. It is
90
+ * a no-op when there is no open turn to stop — during a seal, where the turn the absence was
91
+ * noticed on is already sealed and its successor opens paused instead — and when the clock is
92
+ * stopped already, so a second absent seat neither double-publishes nor disturbs the first pause.
93
+ */
94
+ private pauseForTakeoverVote;
95
+ /**
96
+ * Stop the clock of a match whose last active player has just left.
97
+ *
98
+ * There is nobody left to ask about the empty seat, so no prompt is opened and nothing else would
99
+ * pause the turn. `resumeAfterTakeoverVotes`, which `rejoin` calls, restarts it.
100
+ */
101
+ pauseAbandonedMatch(matchId: string): Promise<void>;
102
+ private clearOpenDeadline;
71
103
  /** Restart a full turn clock after the final outstanding absence vote closes. */
72
104
  resumeAfterTakeoverVotes(matchId: string): Promise<void>;
73
105
  /** Re-run the verdict of every unconfirmed turn and the auto-seal of the open one. */