@chaos-overlords/server 0.8.1 → 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 (57) hide show
  1. package/dist/app.js +10 -0
  2. package/dist/app.js.map +1 -1
  3. package/dist/container.d.ts +16 -0
  4. package/dist/container.d.ts.map +1 -1
  5. package/dist/container.js +5 -0
  6. package/dist/container.js.map +1 -1
  7. package/dist/http/contractJson.d.ts +32 -0
  8. package/dist/http/contractJson.d.ts.map +1 -0
  9. package/dist/http/contractJson.js +22 -0
  10. package/dist/http/contractJson.js.map +1 -0
  11. package/dist/http/errorHandler.d.ts.map +1 -1
  12. package/dist/http/errorHandler.js +45 -6
  13. package/dist/http/errorHandler.js.map +1 -1
  14. package/dist/http/middleware.d.ts +11 -0
  15. package/dist/http/middleware.d.ts.map +1 -1
  16. package/dist/http/middleware.js +71 -5
  17. package/dist/http/middleware.js.map +1 -1
  18. package/dist/http/responseValidation.d.ts.map +1 -1
  19. package/dist/http/responseValidation.js +5 -3
  20. package/dist/http/responseValidation.js.map +1 -1
  21. package/dist/http/types.d.ts +20 -0
  22. package/dist/http/types.d.ts.map +1 -1
  23. package/dist/index.d.ts +3 -2
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.js +3 -2
  26. package/dist/index.js.map +1 -1
  27. package/dist/routes/bugReports.d.ts.map +1 -1
  28. package/dist/routes/bugReports.js +13 -1
  29. package/dist/routes/bugReports.js.map +1 -1
  30. package/dist/routes/events.d.ts.map +1 -1
  31. package/dist/routes/events.js +26 -5
  32. package/dist/routes/events.js.map +1 -1
  33. package/dist/routes/lobby.d.ts.map +1 -1
  34. package/dist/routes/lobby.js +19 -7
  35. package/dist/routes/lobby.js.map +1 -1
  36. package/dist/routes/protocol.d.ts.map +1 -1
  37. package/dist/routes/protocol.js +2 -1
  38. package/dist/routes/protocol.js.map +1 -1
  39. package/dist/routes/snapshots.d.ts.map +1 -1
  40. package/dist/routes/snapshots.js +3 -2
  41. package/dist/routes/snapshots.js.map +1 -1
  42. package/dist/routes/turns.d.ts.map +1 -1
  43. package/dist/routes/turns.js +4 -3
  44. package/dist/routes/turns.js.map +1 -1
  45. package/dist/sse/LocalEventHub.d.ts +36 -10
  46. package/dist/sse/LocalEventHub.d.ts.map +1 -1
  47. package/dist/sse/LocalEventHub.js +75 -16
  48. package/dist/sse/LocalEventHub.js.map +1 -1
  49. package/dist/sse/MatchLog.d.ts +85 -0
  50. package/dist/sse/MatchLog.d.ts.map +1 -0
  51. package/dist/sse/MatchLog.js +147 -0
  52. package/dist/sse/MatchLog.js.map +1 -0
  53. package/dist/sse/createSseResponse.d.ts +27 -2
  54. package/dist/sse/createSseResponse.d.ts.map +1 -1
  55. package/dist/sse/createSseResponse.js +96 -14
  56. package/dist/sse/createSseResponse.js.map +1 -1
  57. package/package.json +7 -7
package/dist/app.js CHANGED
@@ -50,6 +50,10 @@ function apiRoutes() {
50
50
  // handler has produced it. This closes the runtime gap left by TypeScript-only
51
51
  // handler checking: storage values and JSON columns are not trustworthy merely because typed.
52
52
  api.use('*', validateContractResponse);
53
+ // A backstop sized to the largest legitimate body on the API (a bug report's base64 state), so a
54
+ // route added without a cap of its own is still bounded. Every route below narrows it; Hono
55
+ // composes middleware rather than replacing it, so the tightest cap that matches a path wins.
56
+ api.use('*', bodyLimit({ maxSize: BUG_REPORT_LIMITS.stateBase64Bytes + SMALL_BODY }));
53
57
  // The unauthenticated handshake parses a body before any player identity exists, so it needs the
54
58
  // same address budget and small-body cap as the lobby doors it protects.
55
59
  api.use('/handshake', rateLimited, bodyLimit({ maxSize: SMALL_BODY }));
@@ -70,6 +74,12 @@ function apiRoutes() {
70
74
  // the bare path would authenticate (a token lookup plus a match read) and charge the member's
71
75
  // rate limit twice on every request to it.
72
76
  api.use('/matches/:matchId/*', bearerAuth, memberRateLimited());
77
+ // The two member routes with a JSON body that had no cap of their own. A member token costs one
78
+ // unauthenticated `POST /matches`, so without these a stranger could make the process buffer and
79
+ // parse a body of any size, a few hundred megabytes at a time, inside the same process that is
80
+ // sealing every other match's turns.
81
+ api.use('/matches/:matchId/settings', bodyLimit({ maxSize: LIMITS.gameSettingsBytes + SMALL_BODY }));
82
+ api.use('/matches/:matchId/players/:playerId/takeover-vote', bodyLimit({ maxSize: SMALL_BODY }));
73
83
  api.use('/matches/:matchId/turns/:turn/orders', bodyLimit({ maxSize: LIMITS.ordersBytes }));
74
84
  api.use('/matches/:matchId/turns/:turn/report', bodyLimit({ maxSize: SMALL_BODY }));
75
85
  // A snapshot is a megabyte, so uploads carry their own tighter budget on top of the member one.
package/dist/app.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,4BAA4B,CAAA;AACtE,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAC3B,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAA;AAC3C,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEhC,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAA;AACjD,OAAO,EACL,UAAU,EACV,oBAAoB,EACpB,iBAAiB,EACjB,WAAW,EACX,SAAS,GACV,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,wBAAwB,EAAE,MAAM,2BAA2B,CAAA;AAEpE,OAAO,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAA;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAA;AACrD,OAAO,EAAE,yBAAyB,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAA;AACrF,OAAO,EAAE,sBAAsB,EAAE,MAAM,mBAAmB,CAAA;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,oBAAoB,CAAA;AAC3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAEnD,MAAM,CAAC,MAAM,UAAU,GAAG,SAAS,CAAA;AAEnC,MAAM,UAAU,GAAG,EAAE,GAAG,IAAI,CAAA;AAE5B,2FAA2F;AAC3F,MAAM,UAAU,SAAS,CAAC,SAA0B;IAClD,MAAM,GAAG,GAAG,IAAI,IAAI,EAAU,CAAA;IAC9B,GAAG,CAAC,OAAO,CAAC,WAAW,CAAC,CAAA;IACxB,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;QAC7B,CAAC,CAAC,GAAG,CAAC,WAAW,EAAE,SAAS,CAAC,CAAA;QAC7B,MAAM,IAAI,EAAE,CAAA;IACd,CAAC,CAAC,CAAA;IACF,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,CAAA;IACvB,IAAI,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5C,GAAG,CAAC,GAAG,CACL,GAAG,EACH,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,GAAG,SAAS,CAAC,MAAM,CAAC,WAAW,CAAC;YACzC,YAAY,EAAE,CAAC,eAAe,EAAE,cAAc,EAAE,eAAe,CAAC;YAChE,aAAa,EAAE,CAAC,cAAc,CAAC;YAC/B,MAAM,EAAE,GAAG;SACZ,CAAC,CACH,CAAA;IACH,CAAC;IAED,GAAG,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,CAAA;IAC/C,GAAG,CAAC,KAAK,CAAC,UAAU,EAAE,SAAS,EAAE,CAAC,CAAA;IAClC,OAAO,GAAG,CAAA;AACZ,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,SAAS;IAChB,MAAM,GAAG,GAAG,IAAI,IAAI,EAAU,CAAA;IAE9B,8FAA8F;IAC9F,+EAA+E;IAC/E,8FAA8F;IAC9F,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,wBAAwB,CAAC,CAAA;IAEtC,iGAAiG;IACjG,4FAA4F;IAC5F,8FAA8F;IAC9F,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,SAAS,CAAC,EAAE,OAAO,EAAE,iBAAiB,CAAC,gBAAgB,GAAG,UAAU,EAAE,CAAC,CAAC,CAAA;IAErF,iGAAiG;IACjG,yEAAyE;IACzE,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC,CAAA;IACtE,sBAAsB,CAAC,GAAG,CAAC,CAAA;IAE3B,mEAAmE;IACnE,GAAG,CAAC,GAAG,CAAC,UAAU,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,iBAAiB,GAAG,UAAU,EAAE,CAAC,CAAC,CAAA;IAC/F,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC,CAAA;IACzE,iGAAiG;IACjG,+FAA+F;IAC/F,sDAAsD;IACtD,GAAG,CAAC,GAAG,CAAC,uBAAuB,EAAE,WAAW,EAAE,SAAS,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC,CAAA;IACjF,iGAAiG;IACjG,wFAAwF;IACxF,GAAG,CAAC,GAAG,CACL,cAAc,EACd,oBAAoB,EACpB,SAAS,CAAC,EAAE,OAAO,EAAE,iBAAiB,CAAC,gBAAgB,GAAG,UAAU,EAAE,CAAC,CACxE,CAAA;IAED,yBAAyB,CAAC,GAAG,CAAC,CAAA;IAC9B,uBAAuB,CAAC,GAAG,CAAC,CAAA;IAE5B,+FAA+F;IAC/F,8FAA8F;IAC9F,2CAA2C;IAC3C,GAAG,CAAC,GAAG,CAAC,qBAAqB,EAAE,UAAU,EAAE,iBAAiB,EAAE,CAAC,CAAA;IAC/D,gGAAgG;IAChG,iGAAiG;IACjG,+FAA+F;IAC/F,qCAAqC;IACrC,GAAG,CAAC,GAAG,CACL,4BAA4B,EAC5B,SAAS,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,iBAAiB,GAAG,UAAU,EAAE,CAAC,CAC9D,CAAA;IACD,GAAG,CAAC,GAAG,CAAC,mDAAmD,EAAE,SAAS,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC,CAAA;IAChG,GAAG,CAAC,GAAG,CAAC,sCAAsC,EAAE,SAAS,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC,CAAA;IAC3F,GAAG,CAAC,GAAG,CAAC,sCAAsC,EAAE,SAAS,CAAC,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC,CAAA;IACnF,gGAAgG;IAChG,GAAG,CAAC,GAAG,CACL,6BAA6B,EAC7B,iBAAiB,CAAC,QAAQ,CAAC,EAC3B,SAAS,CAAC,EAAE,OAAO,EAAE,MAAM,CAAC,mBAAmB,GAAG,UAAU,EAAE,CAAC,CAChE,CAAA;IAED,yBAAyB,CAAC,GAAG,CAAC,CAAA;IAC9B,kBAAkB,CAAC,GAAG,CAAC,CAAA;IACvB,sBAAsB,CAAC,GAAG,CAAC,CAAA;IAC3B,mBAAmB,CAAC,GAAG,CAAC,CAAA;IACxB,OAAO,GAAG,CAAA;AACZ,CAAC"}
@@ -45,6 +45,17 @@ export interface RateLimiters {
45
45
  * thirty multi-megabyte uploads a minute from a stranger).
46
46
  */
47
47
  bugReport: RateLimiter;
48
+ /**
49
+ * How many attached journals one address may file in a day.
50
+ *
51
+ * The per-minute tier bounds a retry loop; it does nothing about a steady drip. Five reports a
52
+ * minute at six megabytes each is thirty megabytes a minute, so one address could spend the whole
53
+ * 512 MiB global journal budget in about seventeen minutes and every real player's report would
54
+ * lose its journal for the next twenty-four hours. Over this budget the report is still accepted
55
+ * and the journal is dropped, which is what the global budget does too: the description is the
56
+ * part worth keeping.
57
+ */
58
+ bugReportState: RateLimiter;
48
59
  }
49
60
  export interface ServerContainer {
50
61
  kernel: Kernel;
@@ -71,5 +82,10 @@ export declare const DEFAULT_RATE_LIMITS: {
71
82
  * beyond that is a client in a loop uploading whole match journals.
72
83
  */
73
84
  readonly bugReportPerMinute: 5;
85
+ /**
86
+ * Attached journals per address per day. At about six megabytes each this is roughly six per cent
87
+ * of the default global budget, so no single address can crowd everybody else out of it.
88
+ */
89
+ readonly bugReportStatePerDay: 5;
74
90
  };
75
91
  //# sourceMappingURL=container.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AACrF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAE1C,MAAM,WAAW,YAAY;IAC3B;;;;;;;;OAQG;IACH,aAAa,EAAE,OAAO,CAAA;IACtB,+CAA+C;IAC/C,cAAc,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;CAC/B;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,WAAW,CAAA;IACtB,MAAM,EAAE,WAAW,CAAA;IACnB,MAAM,EAAE,WAAW,CAAA;IACnB;;;;;;;OAOG;IACH,SAAS,EAAE,WAAW,CAAA;CACvB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAA;IAC7B,WAAW,EAAE,iBAAiB,CAAA;IAC9B,YAAY,EAAE,YAAY,CAAA;IAC1B,MAAM,EAAE,YAAY,CAAA;IACpB,wFAAwF;IACxF,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,MAAM,CAAA;CAC/C;AAED,eAAO,MAAM,qBAAqB,EAAE,YAInC,CAAA;AAED,sFAAsF;AACtF,eAAO,MAAM,mBAAmB;;;;IAI9B;;;;OAIG;;CAEK,CAAA"}
1
+ {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8BAA8B,CAAA;AACpE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AACrF,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AACnC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAE1C,MAAM,WAAW,YAAY;IAC3B;;;;;;;;OAQG;IACH,aAAa,EAAE,OAAO,CAAA;IACtB,+CAA+C;IAC/C,cAAc,EAAE,MAAM,CAAA;IACtB;;;;OAIG;IACH,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;CAC/B;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,WAAW,CAAA;IACtB,MAAM,EAAE,WAAW,CAAA;IACnB,MAAM,EAAE,WAAW,CAAA;IACnB;;;;;;;OAOG;IACH,SAAS,EAAE,WAAW,CAAA;IACtB;;;;;;;;;OASG;IACH,cAAc,EAAE,WAAW,CAAA;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd;;;OAGG;IACH,UAAU,CAAC,EAAE,gBAAgB,CAAA;IAC7B,WAAW,EAAE,iBAAiB,CAAA;IAC9B,YAAY,EAAE,YAAY,CAAA;IAC1B,MAAM,EAAE,YAAY,CAAA;IACpB,wFAAwF;IACxF,aAAa,CAAC,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,KAAK,MAAM,CAAA;CAC/C;AAED,eAAO,MAAM,qBAAqB,EAAE,YAInC,CAAA;AAED,sFAAsF;AACtF,eAAO,MAAM,mBAAmB;aAC9B,kBAAkB,EAAE,EAAE;aACtB,eAAe,EAAE,GAAG;aACpB,eAAe,EAAE,EAAE;IACnB;;;;OAIG;aACH,kBAAkB,EAAE,CAAC;IACrB;;;OAGG;aACH,oBAAoB,EAAE,CAAC;CACf,CAAA"}
package/dist/container.js CHANGED
@@ -14,5 +14,10 @@ export const DEFAULT_RATE_LIMITS = {
14
14
  * beyond that is a client in a loop uploading whole match journals.
15
15
  */
16
16
  bugReportPerMinute: 5,
17
+ /**
18
+ * Attached journals per address per day. At about six megabytes each this is roughly six per cent
19
+ * of the default global budget, so no single address can crowd everybody else out of it.
20
+ */
21
+ bugReportStatePerDay: 5,
17
22
  };
18
23
  //# sourceMappingURL=container.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"container.js","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AAiEA,MAAM,CAAC,MAAM,qBAAqB,GAAiB;IACjD,aAAa,EAAE,IAAI;IACnB,cAAc,EAAE,MAAM;IACtB,WAAW,EAAE,EAAE;CAChB,CAAA;AAED,sFAAsF;AACtF,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,kBAAkB,EAAE,EAAE;IACtB,eAAe,EAAE,GAAG;IACpB,eAAe,EAAE,EAAE;IACnB;;;;OAIG;IACH,kBAAkB,EAAE,CAAC;CACb,CAAA"}
1
+ {"version":3,"file":"container.js","sourceRoot":"","sources":["../src/container.ts"],"names":[],"mappings":"AA4EA,MAAM,CAAC,MAAM,qBAAqB,GAAiB;IACjD,aAAa,EAAE,IAAI;IACnB,cAAc,EAAE,MAAM;IACtB,WAAW,EAAE,EAAE;CAChB,CAAA;AAED,sFAAsF;AACtF,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,kBAAkB,EAAE,EAAE;IACtB,eAAe,EAAE,GAAG;IACpB,eAAe,EAAE,EAAE;IACnB;;;;OAIG;IACH,kBAAkB,EAAE,CAAC;IACrB;;;OAGG;IACH,oBAAoB,EAAE,CAAC;CACf,CAAA"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The one thing this needs of a context: somewhere to put the value.
3
+ *
4
+ * Structural rather than `Context<AppEnv>`, because a contract route hands its handler a context
5
+ * typed against that route's own environment. Naming only the capability lets the helper be called
6
+ * from both without either side widening.
7
+ */
8
+ interface RecordsResponseBody {
9
+ set(key: 'responseBody', value: {
10
+ value: unknown;
11
+ }): void;
12
+ }
13
+ /**
14
+ * Hands `value` straight back, having noted it on the context for the response validator.
15
+ *
16
+ * Validation happens after the handler, and the only thing it used to be handed was the finished
17
+ * `Response`: to see the value again it cloned the body, parsed the JSON back out and walked the
18
+ * schema over the result. For `GET /snapshots/latest` that is a megabyte stringified, a megabyte
19
+ * cloned, a megabyte parsed and a base64 regex over the result, on every reconnect and every repair
20
+ * adoption. For a sealed set it is up to six order documents parsed and walked a second time.
21
+ *
22
+ * The object the handler produced is the thing worth checking, and it is right here. Noting it
23
+ * turns the second pass into a schema walk over values already in memory, and the body is
24
+ * stringified exactly once.
25
+ *
26
+ * It wraps the value rather than the call to `c.json` so the contract adapter keeps type-checking
27
+ * the handler's response against the route it is mounted from, which is the other half of the same
28
+ * guarantee. Written inline at the return: `return c.json(answering(c, view), 200)`.
29
+ */
30
+ export declare function answering<T>(c: RecordsResponseBody, value: T): T;
31
+ export {};
32
+ //# sourceMappingURL=contractJson.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contractJson.d.ts","sourceRoot":"","sources":["../../src/http/contractJson.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,UAAU,mBAAmB;IAC3B,GAAG,CAAC,GAAG,EAAE,cAAc,EAAE,KAAK,EAAE;QAAE,KAAK,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAA;CAC1D;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CAAC,CAAC,EAAE,CAAC,EAAE,mBAAmB,EAAE,KAAK,EAAE,CAAC,GAAG,CAAC,CAGhE"}
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Hands `value` straight back, having noted it on the context for the response validator.
3
+ *
4
+ * Validation happens after the handler, and the only thing it used to be handed was the finished
5
+ * `Response`: to see the value again it cloned the body, parsed the JSON back out and walked the
6
+ * schema over the result. For `GET /snapshots/latest` that is a megabyte stringified, a megabyte
7
+ * cloned, a megabyte parsed and a base64 regex over the result, on every reconnect and every repair
8
+ * adoption. For a sealed set it is up to six order documents parsed and walked a second time.
9
+ *
10
+ * The object the handler produced is the thing worth checking, and it is right here. Noting it
11
+ * turns the second pass into a schema walk over values already in memory, and the body is
12
+ * stringified exactly once.
13
+ *
14
+ * It wraps the value rather than the call to `c.json` so the contract adapter keeps type-checking
15
+ * the handler's response against the route it is mounted from, which is the other half of the same
16
+ * guarantee. Written inline at the return: `return c.json(answering(c, view), 200)`.
17
+ */
18
+ export function answering(c, value) {
19
+ c.set('responseBody', { value });
20
+ return value;
21
+ }
22
+ //# sourceMappingURL=contractJson.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"contractJson.js","sourceRoot":"","sources":["../../src/http/contractJson.ts"],"names":[],"mappings":"AAWA;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,SAAS,CAAI,CAAsB,EAAE,KAAQ;IAC3D,CAAC,CAAC,GAAG,CAAC,cAAc,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;IAChC,OAAO,KAAK,CAAA;AACd,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"errorHandler.d.ts","sourceRoot":"","sources":["../../src/http/errorHandler.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAErC,kGAAkG;AAClG,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,QAAQ,CA0BtE"}
1
+ {"version":3,"file":"errorHandler.d.ts","sourceRoot":"","sources":["../../src/http/errorHandler.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAA;AAEnC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAErC,kGAAkG;AAClG,wBAAgB,WAAW,CAAC,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,GAAG,QAAQ,CAwCtE"}
@@ -6,23 +6,43 @@ import { HTTPException } from 'hono/http-exception';
6
6
  export function handleError(error, c) {
7
7
  const requestId = c.get('requestId');
8
8
  if (isDomainError(error)) {
9
- return respond(c, error.code, error.message, error.details, requestId);
9
+ return respond(c, {
10
+ code: error.code,
11
+ message: error.message,
12
+ details: error.details,
13
+ requestId,
14
+ });
10
15
  }
11
16
  if (error instanceof SchemaValidationError) {
12
17
  // The contract validator refused a path param, a query value or the body. The issue paths and
13
18
  // messages come straight from valibot rather than being restated per endpoint, so a client is
14
19
  // told which field it got wrong.
15
- return respond(c, 'validation_failed', 'Request failed contract validation', { reason: 'invalid_request', issues: error.issues.map(describeIssue) }, requestId);
20
+ return respond(c, {
21
+ code: 'validation_failed',
22
+ message: 'Request failed contract validation',
23
+ details: { reason: 'invalid_request', issues: error.issues.map(describeIssue) },
24
+ requestId,
25
+ });
16
26
  }
17
27
  if (error instanceof HTTPException) {
18
28
  const code = codeForStatus(error.status);
19
- return respond(c, code, error.message || code, { reason: `http_${error.status}` }, requestId);
29
+ return respond(c, {
30
+ code,
31
+ message: error.message || code,
32
+ details: { reason: `http_${error.status}` },
33
+ requestId,
34
+ });
20
35
  }
21
36
  c.get('container')?.kernel.deps.logger.error('unhandled request error', {
22
37
  requestId,
23
38
  error: error.stack ?? String(error),
24
39
  });
25
- return respond(c, 'internal', 'Internal server error', { reason: 'internal' }, requestId);
40
+ return respond(c, {
41
+ code: 'internal',
42
+ message: 'Internal server error',
43
+ details: { reason: 'internal' },
44
+ requestId,
45
+ });
26
46
  }
27
47
  /**
28
48
  * What a client is told about a refused field: where and why, never what it sent.
@@ -33,7 +53,26 @@ export function handleError(error, c) {
33
53
  */
34
54
  function describeIssue(issue) {
35
55
  const path = (issue.path ?? []).map((segment) => String(typeof segment === 'object' && segment !== null && 'key' in segment ? segment.key : segment));
36
- return { message: issue.message, path };
56
+ return { message: describeMessage(issue), path };
57
+ }
58
+ /**
59
+ * The message with the received value taken back out of it.
60
+ *
61
+ * Stripping valibot's `input` and `received` FIELDS was not enough, because its default message
62
+ * embeds the same value in prose: every schema step without a message of its own reports
63
+ * `Invalid type: Expected string but received "<value>"`, and the same shape covers `literal`,
64
+ * `picklist`, `minValue` and `maxValue`. So `{ "password": 123456 }` answered with the password,
65
+ * and `"visibility": "secret"` answered with the word. A wrong password that is at least a STRING
66
+ * fails on its length instead and was never echoed, which is why the conformance case for the
67
+ * no-echo rule passed while the rule did not hold.
68
+ *
69
+ * What the caller needs is which field and what was expected; what it sent, it already knows.
70
+ */
71
+ function describeMessage(issue) {
72
+ if (issue.received === undefined)
73
+ return issue.message;
74
+ const withoutReceived = issue.message.replace(/\s*but received\b.*$/is, '');
75
+ return withoutReceived === '' ? 'Invalid value' : withoutReceived;
37
76
  }
38
77
  /**
39
78
  * The error code a framework `HTTPException` maps to. Written out rather than derived by scanning
@@ -53,7 +92,7 @@ const CODE_BY_STATUS = {
53
92
  function codeForStatus(status) {
54
93
  return CODE_BY_STATUS[status] ?? 'internal';
55
94
  }
56
- function respond(c, code, message, details, requestId) {
95
+ function respond(c, { code, message, details, requestId }) {
57
96
  const body = { error: { code, message, details, requestId } };
58
97
  return c.json(validateSync(errorEnvelopeSchema, body), STATUS_BY_CODE[code]);
59
98
  }
@@ -1 +1 @@
1
- {"version":3,"file":"errorHandler.js","sourceRoot":"","sources":["../../src/http/errorHandler.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"errorHandler.js","sourceRoot":"","sources":["../../src/http/errorHandler.ts"],"names":[],"mappings":"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"}
@@ -30,6 +30,17 @@ export declare const bugReportRateLimited: MiddlewareHandler<AppEnv>;
30
30
  * address cannot spend another's budget. Must run after `bearerAuth`.
31
31
  */
32
32
  export declare function memberRateLimited(tier?: keyof RateLimiters): MiddlewareHandler<AppEnv>;
33
+ /**
34
+ * The budget key for a client address.
35
+ *
36
+ * Two normalisations, both because the raw string is not one client. A routed IPv6 /64 gives a
37
+ * client 2^64 source addresses, so every request could carry a new key and no per-address budget
38
+ * would ever bind; and proxies that write `ip:port` into `X-Forwarded-For` (Azure Application
39
+ * Gateway does) give a new port per connection, with the same result. Each unrecognised key also
40
+ * costs a map entry that lives up to two windows, so the limiter's own memory grew with the flood
41
+ * it was meant to stop.
42
+ */
43
+ export declare function rateLimitKey(address: string): string;
33
44
  /**
34
45
  * The client address as a trusted proxy reports it.
35
46
  *
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/http/middleware.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAErC;;;;;GAKG;AACH,eAAO,MAAM,SAAS,EAAE,iBAAiB,CAAC,MAAM,CAK/C,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,EAAE,iBAAiB,CAAC,MAAM,CAgBhD,CAAA;AAgBD,kFAAkF;AAClF,eAAO,MAAM,WAAW,EAAE,iBAAiB,CAAC,MAAM,CAKjD,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,iBAAiB,CAAC,MAAM,CAK1D,CAAA;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,MAAM,YAAuB,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAMhG;AAiBD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAClC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,EAClB,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GACpD,MAAM,CAaR"}
1
+ {"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../../src/http/middleware.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AACtD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAErC;;;;;GAKG;AACH,eAAO,MAAM,SAAS,EAAE,iBAAiB,CAAC,MAAM,CAK/C,CAAA;AAgBD;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,EAAE,iBAAiB,CAAC,MAAM,CAgBhD,CAAA;AAgBD,kFAAkF;AAClF,eAAO,MAAM,WAAW,EAAE,iBAAiB,CAAC,MAAM,CASjD,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,oBAAoB,EAAE,iBAAiB,CAAC,MAAM,CAe1D,CAAA;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,GAAE,MAAM,YAAuB,GAAG,iBAAiB,CAAC,MAAM,CAAC,CAMhG;AAQD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAiBpD;AA2BD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,oBAAoB,CAClC,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,EAClB,OAAO,GAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GACpD,MAAM,CAaR"}
@@ -6,11 +6,24 @@ import { RateLimitedError, UnauthorizedError } from '@chaos-overlords/kernel';
6
6
  * here, and the response most worth correlating is exactly the one that failed.
7
7
  */
8
8
  export const requestId = async (c, next) => {
9
- const id = c.req.header('x-request-id')?.slice(0, 64) || crypto.randomUUID();
9
+ const id = safeRequestId(c.req.header('x-request-id')) ?? crypto.randomUUID();
10
10
  c.set('requestId', id);
11
11
  c.header('X-Request-Id', id);
12
12
  await next();
13
13
  };
14
+ /**
15
+ * A client-supplied correlation id, or null when it is not one.
16
+ *
17
+ * The value is echoed in a response header and written into the structured logs, so it is a string
18
+ * a stranger chooses that ends up in an operator's tooling. Restricting it to the characters ids
19
+ * are actually made of leaves header splitting, log-line forgery and terminal escapes with nothing
20
+ * to work with; anything else is simply replaced by a minted one, because a caller sending a
21
+ * malformed id wanted correlation, not a refusal.
22
+ */
23
+ function safeRequestId(raw) {
24
+ const trimmed = raw?.trim() ?? '';
25
+ return trimmed !== '' && trimmed.length <= 64 && /^[A-Za-z0-9_-]+$/.test(trimmed) ? trimmed : null;
26
+ }
14
27
  /**
15
28
  * Resolves the bearer token to a principal or refuses with 401.
16
29
  *
@@ -48,14 +61,18 @@ export const bearerAuth = async (c, next) => {
48
61
  */
49
62
  function chargeFailedAuth(c) {
50
63
  const container = c.get('container');
51
- const key = (container.clientAddress ?? defaultClientAddress)(c);
64
+ const key = addressOf(c);
52
65
  enforce(container.rateLimiters, 'anonymous', key, c);
53
66
  }
54
67
  /** Fixed-window limiter on the unauthenticated doors, keyed by client address. */
55
68
  export const rateLimited = async (c, next) => {
56
69
  const container = c.get('container');
57
- const key = (container.clientAddress ?? defaultClientAddress)(c);
70
+ const key = addressOf(c);
58
71
  enforce(container.rateLimiters, 'anonymous', key, c);
72
+ // The join doors charge a per-caller budget of their own in front of PBKDF2, in the kernel,
73
+ // where there is no request to work an address out from. Normalised here so that one client is
74
+ // one key there too; see `rateLimitKey`.
75
+ c.set('caller', rateLimitKey(key));
59
76
  await next();
60
77
  };
61
78
  /**
@@ -65,8 +82,14 @@ export const rateLimited = async (c, next) => {
65
82
  */
66
83
  export const bugReportRateLimited = async (c, next) => {
67
84
  const container = c.get('container');
68
- const key = (container.clientAddress ?? defaultClientAddress)(c);
85
+ const key = addressOf(c);
69
86
  enforce(container.rateLimiters, 'bugReport', key, c);
87
+ // The daily journal budget is NOT spent here. It used to be, before the body had even been read,
88
+ // so five text-only reports or five requests the contract validator answered 413 or 422 for spent
89
+ // it, and the sixth — the one that actually carried a journal — was quietly filed with
90
+ // `stateStored: 'omitted'`. Everyone behind one NAT shares those five. The handler spends it
91
+ // instead, once it knows there is a journal to spend it on; see the bug report route.
92
+ c.set('bugReportJournalBudget', () => container.rateLimiters.bugReportState.take(`bugReportState:${rateLimitKey(key)}`) === null);
70
93
  await next();
71
94
  };
72
95
  /**
@@ -80,8 +103,51 @@ export function memberRateLimited(tier = 'member') {
80
103
  await next();
81
104
  };
82
105
  }
106
+ /** The client address this deployment attributes a request to, before it is normalised. */
107
+ function addressOf(c) {
108
+ const container = c.get('container');
109
+ return (container.clientAddress ?? defaultClientAddress)(c);
110
+ }
111
+ /**
112
+ * The budget key for a client address.
113
+ *
114
+ * Two normalisations, both because the raw string is not one client. A routed IPv6 /64 gives a
115
+ * client 2^64 source addresses, so every request could carry a new key and no per-address budget
116
+ * would ever bind; and proxies that write `ip:port` into `X-Forwarded-For` (Azure Application
117
+ * Gateway does) give a new port per connection, with the same result. Each unrecognised key also
118
+ * costs a map entry that lives up to two windows, so the limiter's own memory grew with the flood
119
+ * it was meant to stop.
120
+ */
121
+ export function rateLimitKey(address) {
122
+ const trimmed = address.trim();
123
+ if (trimmed === '')
124
+ return 'unknown';
125
+ // `[2001:db8::1]:443` — a bracketed IPv6 host with a port.
126
+ const bracketed = /^\[([^\]]+)](?::\d+)?$/.exec(trimmed);
127
+ const host = bracketed?.[1] ?? trimmed;
128
+ // `1.2.3.4:443` — an IPv4 host with a port. A bare IPv6 address has more than one colon.
129
+ const withoutPort = /^[^:]+:\d+$/.test(host) ? host.split(':')[0] : host;
130
+ if (!withoutPort.includes(':'))
131
+ return withoutPort;
132
+ // IPv6, masked to the /64 a single client is routed. IPv4-mapped forms keep their full address.
133
+ if (withoutPort.includes('.'))
134
+ return withoutPort;
135
+ const groups = withoutPort.toLowerCase().split('::');
136
+ if (groups.length > 1) {
137
+ const leading = groups[0].split(':').filter((part) => part !== '');
138
+ return leading.length >= 4 ? `${leading.slice(0, 4).join(':')}::/64` : `${withoutPort}::/64`;
139
+ }
140
+ return `${withoutPort.split(':').slice(0, 4).join(':')}::/64`;
141
+ }
142
+ /**
143
+ * Tiers keyed by something that is already a single identity rather than a client address.
144
+ *
145
+ * `rateLimitKey` masks IPv6 prefixes and strips ports, which is meaningless work on a player UUID
146
+ * and runs on every authenticated request.
147
+ */
148
+ const IDENTITY_TIERS = new Set(['member', 'upload']);
83
149
  function enforce(limiters, tier, key, c) {
84
- const retryAfter = limiters[tier].take(`${tier}:${key}`);
150
+ const retryAfter = limiters[tier].take(`${tier}:${IDENTITY_TIERS.has(tier) ? key : rateLimitKey(key)}`);
85
151
  if (retryAfter === null)
86
152
  return;
87
153
  c.header('Retry-After', String(retryAfter));
@@ -1 +1 @@
1
- {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/http/middleware.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/http/middleware.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"responseValidation.d.ts","sourceRoot":"","sources":["../../src/http/responseValidation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAC7C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAErC;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBAAiB,CAAC,MAAM,CA6B9D,CAAA;AAED,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO;CAI7C"}
1
+ {"version":3,"file":"responseValidation.d.ts","sourceRoot":"","sources":["../../src/http/responseValidation.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,MAAM,CAAA;AAC7C,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,SAAS,CAAA;AAErC;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,EAAE,iBAAiB,CAAC,MAAM,CA+B9D,CAAA;AAED,qBAAa,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,OAAO,EAG3C;CACF"}
@@ -19,10 +19,12 @@ export const validateContractResponse = async (c, next) => {
19
19
  }
20
20
  if (responseKind.kind !== 'json')
21
21
  return;
22
- let body;
23
22
  try {
24
- body = await c.res.clone().json();
25
- await validate(responseKind.schema, body);
23
+ // The handler's own object when it recorded one (`contractJson`), which is every contract
24
+ // route: checking that is checking what was serialised, without serialising it again. A
25
+ // response that arrives without one is still read back rather than trusted.
26
+ const recorded = c.get('responseBody');
27
+ await validate(responseKind.schema, recorded ? recorded.value : await c.res.clone().json());
26
28
  }
27
29
  catch (error) {
28
30
  throw new ContractResponseError(`status ${c.res.status} body does not match the contract`, error);
@@ -1 +1 @@
1
- {"version":3,"file":"responseValidation.js","sourceRoot":"","sources":["../../src/http/responseValidation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAIrE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAA8B,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;IACnF,MAAM,IAAI,EAAE,CAAA;IACZ,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;IACrC,IAAI,CAAC,QAAQ;QAAE,OAAM;IAErB,MAAM,WAAW,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,SAAS,CAAA;IAClE,MAAM,YAAY,GAAG,oBAAoB,CACvC,QAAQ,CAAC,qBAAqB,EAC9B,CAAC,CAAC,GAAG,CAAC,MAAM,EACZ,WAAW,EACX,IAAI,CACL,CAAA;IACD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,qBAAqB,CAC7B,UAAU,CAAC,CAAC,GAAG,CAAC,MAAM,sBAAsB,WAAW,IAAI,QAAQ,yBAAyB,CAC7F,CAAA;IACH,CAAC;IACD,IAAI,YAAY,CAAC,IAAI,KAAK,MAAM;QAAE,OAAM;IAExC,IAAI,IAAa,CAAA;IACjB,IAAI,CAAC;QACH,IAAI,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAA;QACjC,MAAM,QAAQ,CAAC,YAAY,CAAC,MAAM,EAAE,IAAI,CAAC,CAAA;IAC3C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,qBAAqB,CAC7B,UAAU,CAAC,CAAC,GAAG,CAAC,MAAM,mCAAmC,EACzD,KAAK,CACN,CAAA;IACH,CAAC;AACH,CAAC,CAAA;AAED,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAe,EAAE,KAAe;QAC1C,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;QACzB,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAA;IACrC,CAAC;CACF"}
1
+ {"version":3,"file":"responseValidation.js","sourceRoot":"","sources":["../../src/http/responseValidation.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,oBAAoB,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAIrE;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAA8B,KAAK,EAAE,CAAC,EAAE,IAAI,EAAE,EAAE;IACnF,MAAM,IAAI,EAAE,CAAA;IACZ,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAA;IACrC,IAAI,CAAC,QAAQ;QAAE,OAAM;IAErB,MAAM,WAAW,GAAG,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,SAAS,CAAA;IAClE,MAAM,YAAY,GAAG,oBAAoB,CACvC,QAAQ,CAAC,qBAAqB,EAC9B,CAAC,CAAC,GAAG,CAAC,MAAM,EACZ,WAAW,EACX,IAAI,CACL,CAAA;IACD,IAAI,CAAC,YAAY,EAAE,CAAC;QAClB,MAAM,IAAI,qBAAqB,CAC7B,UAAU,CAAC,CAAC,GAAG,CAAC,MAAM,sBAAsB,WAAW,IAAI,QAAQ,yBAAyB,CAC7F,CAAA;IACH,CAAC;IACD,IAAI,YAAY,CAAC,IAAI,KAAK,MAAM;QAAE,OAAM;IAExC,IAAI,CAAC;QACH,0FAA0F;QAC1F,wFAAwF;QACxF,4EAA4E;QAC5E,MAAM,QAAQ,GAAG,CAAC,CAAC,GAAG,CAAC,cAAc,CAAC,CAAA;QACtC,MAAM,QAAQ,CAAC,YAAY,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,CAAA;IAC7F,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,qBAAqB,CAC7B,UAAU,CAAC,CAAC,GAAG,CAAC,MAAM,mCAAmC,EACzD,KAAK,CACN,CAAA;IACH,CAAC;AACH,CAAC,CAAA;AAED,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAe,EAAE,KAAe;QAC1C,KAAK,CAAC,OAAO,EAAE,EAAE,KAAK,EAAE,CAAC,CAAA;QACzB,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAA;IACrC,CAAC;CACF"}
@@ -8,6 +8,26 @@ export interface AppEnv {
8
8
  principal: Principal;
9
9
  /** Set by the contract route before the handler runs; absent on non-contract routes. */
10
10
  apiContract?: ApiContract;
11
+ /**
12
+ * Who this request is attributed to, normalised the way the limiters key on. Set by
13
+ * `rateLimited`, read by the handlers that charge a budget of their own below the transport —
14
+ * the join doors, whose password check is PBKDF2 — and absent on every other route.
15
+ */
16
+ caller?: string;
17
+ /**
18
+ * Spends one unit of this caller's daily attached-journal budget and says whether there was
19
+ * any left. Set by `bugReportRateLimited`, called by the handler and only for a report that
20
+ * actually carries a journal; absent everywhere else.
21
+ */
22
+ bugReportJournalBudget?: () => boolean;
23
+ /**
24
+ * The object a contract handler answered with, recorded by `contractJson` so the response
25
+ * validator can check it without parsing the body back out. Wrapped so that a handler
26
+ * answering `null` is still told apart from a handler that recorded nothing.
27
+ */
28
+ responseBody?: {
29
+ value: unknown;
30
+ };
11
31
  };
12
32
  }
13
33
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/http/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAEnD,MAAM,WAAW,MAAM;IACrB,SAAS,EAAE;QACT,SAAS,EAAE,eAAe,CAAA;QAC1B,SAAS,EAAE,MAAM,CAAA;QACjB,SAAS,EAAE,SAAS,CAAA;QACpB,wFAAwF;QACxF,WAAW,CAAC,EAAE,WAAW,CAAA;KAC1B,CAAA;CACF"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/http/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAA;AACxD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAA;AACvD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AAEnD,MAAM,WAAW,MAAM;IACrB,SAAS,EAAE;QACT,SAAS,EAAE,eAAe,CAAA;QAC1B,SAAS,EAAE,MAAM,CAAA;QACjB,SAAS,EAAE,SAAS,CAAA;QACpB,wFAAwF;QACxF,WAAW,CAAC,EAAE,WAAW,CAAA;QACzB;;;;WAIG;QACH,MAAM,CAAC,EAAE,MAAM,CAAA;QACf;;;;WAIG;QACH,sBAAsB,CAAC,EAAE,MAAM,OAAO,CAAA;QACtC;;;;WAIG;QACH,YAAY,CAAC,EAAE;YAAE,KAAK,EAAE,OAAO,CAAA;SAAE,CAAA;KAClC,CAAA;CACF"}
package/dist/index.d.ts CHANGED
@@ -2,6 +2,7 @@ export { API_PREFIX, createApp } from './app';
2
2
  export { DEFAULT_RATE_LIMITS, DEFAULT_SERVER_CONFIG, type RateLimiters, type ServerConfig, type ServerContainer, } from './container';
3
3
  export { bugReportRateLimited, defaultClientAddress } from './http/middleware';
4
4
  export type { AppEnv } from './http/types';
5
- export { createSseResponse, type EventStreamSource, formatEvent } from './sse/createSseResponse';
6
- export { DEFAULT_EVENT_HUB_LIMITS, type EventHubLimits, LocalEventHub, } from './sse/LocalEventHub';
5
+ export { createSseResponse, type EventStreamSource, formatEvent, isReadableEvent, } from './sse/createSseResponse';
6
+ export { type EventFrame, MatchLog } from './sse/MatchLog';
7
+ export { DEFAULT_EVENT_HUB_LIMITS, type EventHubLimits, LocalEventHub } from './sse/LocalEventHub';
7
8
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAC7C,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,eAAe,GACrB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAC9E,YAAY,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EAAE,iBAAiB,EAAE,KAAK,iBAAiB,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAChG,OAAO,EACL,wBAAwB,EACxB,KAAK,cAAc,EACnB,aAAa,GACd,MAAM,qBAAqB,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAC7C,OAAO,EACL,mBAAmB,EACnB,qBAAqB,EACrB,KAAK,YAAY,EACjB,KAAK,YAAY,EACjB,KAAK,eAAe,GACrB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAC9E,YAAY,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AAC1C,OAAO,EACL,iBAAiB,EACjB,KAAK,iBAAiB,EACtB,WAAW,EACX,eAAe,GAChB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAE,KAAK,UAAU,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC1D,OAAO,EAAE,wBAAwB,EAAE,KAAK,cAAc,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAA"}
package/dist/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  export { API_PREFIX, createApp } from './app';
2
2
  export { DEFAULT_RATE_LIMITS, DEFAULT_SERVER_CONFIG, } from './container';
3
3
  export { bugReportRateLimited, defaultClientAddress } from './http/middleware';
4
- export { createSseResponse, formatEvent } from './sse/createSseResponse';
5
- export { DEFAULT_EVENT_HUB_LIMITS, LocalEventHub, } from './sse/LocalEventHub';
4
+ export { createSseResponse, formatEvent, isReadableEvent, } from './sse/createSseResponse';
5
+ export { MatchLog } from './sse/MatchLog';
6
+ export { DEFAULT_EVENT_HUB_LIMITS, LocalEventHub } from './sse/LocalEventHub';
6
7
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAC7C,OAAO,EACL,mBAAmB,EACnB,qBAAqB,GAItB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAE9E,OAAO,EAAE,iBAAiB,EAA0B,WAAW,EAAE,MAAM,yBAAyB,CAAA;AAChG,OAAO,EACL,wBAAwB,EAExB,aAAa,GACd,MAAM,qBAAqB,CAAA"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAC7C,OAAO,EACL,mBAAmB,EACnB,qBAAqB,GAItB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,oBAAoB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AAE9E,OAAO,EACL,iBAAiB,EAEjB,WAAW,EACX,eAAe,GAChB,MAAM,yBAAyB,CAAA;AAChC,OAAO,EAAmB,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC1D,OAAO,EAAE,wBAAwB,EAAuB,aAAa,EAAE,MAAM,qBAAqB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"bugReports.d.ts","sourceRoot":"","sources":["../../src/routes/bugReports.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAEhC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAE3C;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAU/D"}
1
+ {"version":3,"file":"bugReports.d.ts","sourceRoot":"","sources":["../../src/routes/bugReports.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAGhC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAE3C;;;;;;;GAOG;AACH,wBAAgB,uBAAuB,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAqB/D"}
@@ -1,5 +1,6 @@
1
1
  import { submitBugReportContract } from '@chaos-overlords/contracts';
2
2
  import { NotFoundError } from '@chaos-overlords/kernel';
3
+ import { answering } from '../http/contractJson';
3
4
  import { buildHonoRoute } from '../http/routes';
4
5
  /**
5
6
  * The bug report door.
@@ -17,7 +18,18 @@ export function registerBugReportRoutes(api) {
17
18
  reason: 'bug_reports_disabled',
18
19
  });
19
20
  }
20
- return c.json(await bugReports.submit(c.req.valid('json')), 201);
21
+ const request = c.req.valid('json');
22
+ // The daily journal budget is spent HERE: after the contract has validated the body, and only
23
+ // when there is a journal to spend it on. Charging it in the middleware meant five text-only
24
+ // reports, or five bodies the validator refused, used up the day's allowance for everyone
25
+ // behind one address — and the sixth report, the one carrying the journal somebody wanted, was
26
+ // filed without it and said so only in the receipt.
27
+ //
28
+ // An address that really has spent it files the description without the journal, and the
29
+ // receipt says `omitted` — the same outcome the global byte budget produces.
30
+ const allowed = request.state === undefined || (c.get('bugReportJournalBudget')?.() ?? true);
31
+ const submitted = allowed ? request : { ...request, state: undefined };
32
+ return c.json(answering(c, await bugReports.submit(submitted)), 201);
21
33
  });
22
34
  }
23
35
  //# sourceMappingURL=bugReports.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"bugReports.js","sourceRoot":"","sources":["../../src/routes/bugReports.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAA;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAEvD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAG/C;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CAAC,GAAiB;IACvD,cAAc,CAAC,GAAG,EAAE,uBAAuB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACvD,MAAM,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QACzC,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,aAAa,CAAC,yCAAyC,EAAE;gBACjE,MAAM,EAAE,sBAAsB;aAC/B,CAAC,CAAA;QACJ,CAAC;QACD,OAAO,CAAC,CAAC,IAAI,CAAC,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;IAClE,CAAC,CAAC,CAAA;AACJ,CAAC"}
1
+ {"version":3,"file":"bugReports.js","sourceRoot":"","sources":["../../src/routes/bugReports.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,uBAAuB,EAAE,MAAM,4BAA4B,CAAA;AACpE,OAAO,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAA;AAEvD,OAAO,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAA;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAG/C;;;;;;;GAOG;AACH,MAAM,UAAU,uBAAuB,CAAC,GAAiB;IACvD,cAAc,CAAC,GAAG,EAAE,uBAAuB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QACvD,MAAM,EAAE,UAAU,EAAE,GAAG,CAAC,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;QACzC,IAAI,CAAC,UAAU,EAAE,CAAC;YAChB,MAAM,IAAI,aAAa,CAAC,yCAAyC,EAAE;gBACjE,MAAM,EAAE,sBAAsB;aAC/B,CAAC,CAAA;QACJ,CAAC;QACD,MAAM,OAAO,GAAG,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,CAAA;QACnC,8FAA8F;QAC9F,6FAA6F;QAC7F,0FAA0F;QAC1F,+FAA+F;QAC/F,oDAAoD;QACpD,EAAE;QACF,yFAAyF;QACzF,6EAA6E;QAC7E,MAAM,OAAO,GAAG,OAAO,CAAC,KAAK,KAAK,SAAS,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,wBAAwB,CAAC,EAAE,EAAE,IAAI,IAAI,CAAC,CAAA;QAC5F,MAAM,SAAS,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAA;QACtE,OAAO,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,MAAM,UAAU,CAAC,MAAM,CAAC,SAAS,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;IACtE,CAAC,CAAC,CAAA;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/routes/events.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAGhC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAE3C,4FAA4F;AAC5F,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CAoB3D"}
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/routes/events.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AAIhC,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAG3C,4FAA4F;AAC5F,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,IAAI,CAAC,MAAM,CAAC,GAAG,IAAI,CA0C3D"}