@fuzdev/fuz_app 0.63.0 → 0.64.0

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 (107) hide show
  1. package/dist/actions/CLAUDE.md +124 -11
  2. package/dist/actions/connection_closer.d.ts +68 -0
  3. package/dist/actions/connection_closer.d.ts.map +1 -0
  4. package/dist/actions/connection_closer.js +41 -0
  5. package/dist/actions/register_action_ws.d.ts.map +1 -1
  6. package/dist/actions/register_action_ws.js +23 -2
  7. package/dist/actions/register_ws_endpoint.d.ts +11 -9
  8. package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
  9. package/dist/actions/register_ws_endpoint.js +5 -5
  10. package/dist/actions/transports_ws_auth_guard.d.ts +24 -8
  11. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  12. package/dist/actions/transports_ws_auth_guard.js +23 -7
  13. package/dist/actions/ws_endpoint_spec.d.ts +119 -0
  14. package/dist/actions/ws_endpoint_spec.d.ts.map +1 -0
  15. package/dist/actions/ws_endpoint_spec.js +13 -0
  16. package/dist/auth/CLAUDE.md +79 -15
  17. package/dist/auth/account_action_specs.d.ts +1 -1
  18. package/dist/auth/account_actions.d.ts +13 -0
  19. package/dist/auth/account_actions.d.ts.map +1 -1
  20. package/dist/auth/account_actions.js +31 -1
  21. package/dist/auth/account_routes.d.ts +12 -2
  22. package/dist/auth/account_routes.d.ts.map +1 -1
  23. package/dist/auth/account_routes.js +55 -8
  24. package/dist/auth/account_schema.d.ts +3 -3
  25. package/dist/auth/admin_action_specs.d.ts +8 -8
  26. package/dist/auth/admin_actions.d.ts +11 -0
  27. package/dist/auth/admin_actions.d.ts.map +1 -1
  28. package/dist/auth/admin_actions.js +25 -0
  29. package/dist/auth/audit_emitter.d.ts +56 -12
  30. package/dist/auth/audit_emitter.d.ts.map +1 -1
  31. package/dist/auth/audit_emitter.js +38 -12
  32. package/dist/auth/audit_log_schema.d.ts +5 -3
  33. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  34. package/dist/auth/audit_log_schema.js +5 -3
  35. package/dist/auth/bootstrap_routes.d.ts +1 -1
  36. package/dist/auth/invite_schema.d.ts +2 -2
  37. package/dist/auth/signup_routes.d.ts +1 -1
  38. package/dist/auth/standard_rpc_actions.d.ts +1 -0
  39. package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
  40. package/dist/auth/standard_rpc_actions.js +1 -0
  41. package/dist/http/CLAUDE.md +26 -10
  42. package/dist/http/ip_canonical.d.ts +99 -0
  43. package/dist/http/ip_canonical.d.ts.map +1 -0
  44. package/dist/http/ip_canonical.js +191 -0
  45. package/dist/http/origin.d.ts +13 -5
  46. package/dist/http/origin.d.ts.map +1 -1
  47. package/dist/http/origin.js +13 -31
  48. package/dist/http/pending_effects.d.ts +1 -1
  49. package/dist/http/pending_effects.js +1 -1
  50. package/dist/http/proxy.d.ts +13 -5
  51. package/dist/http/proxy.d.ts.map +1 -1
  52. package/dist/http/proxy.js +15 -23
  53. package/dist/http/surface.d.ts +50 -0
  54. package/dist/http/surface.d.ts.map +1 -1
  55. package/dist/http/surface.js +27 -1
  56. package/dist/primitive_schemas.d.ts +20 -4
  57. package/dist/primitive_schemas.d.ts.map +1 -1
  58. package/dist/primitive_schemas.js +25 -4
  59. package/dist/realtime/sse_auth_guard.d.ts +16 -4
  60. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  61. package/dist/realtime/sse_auth_guard.js +15 -3
  62. package/dist/server/app_backend.d.ts +66 -19
  63. package/dist/server/app_backend.d.ts.map +1 -1
  64. package/dist/server/app_backend.js +57 -34
  65. package/dist/server/app_server.d.ts +60 -0
  66. package/dist/server/app_server.d.ts.map +1 -1
  67. package/dist/server/app_server.js +95 -2
  68. package/dist/server/startup.d.ts.map +1 -1
  69. package/dist/server/startup.js +12 -0
  70. package/dist/testing/CLAUDE.md +64 -28
  71. package/dist/testing/admin_integration.d.ts.map +1 -1
  72. package/dist/testing/admin_integration.js +4 -5
  73. package/dist/testing/adversarial_headers.d.ts +6 -0
  74. package/dist/testing/adversarial_headers.d.ts.map +1 -1
  75. package/dist/testing/adversarial_headers.js +13 -5
  76. package/dist/testing/app_server.d.ts +33 -32
  77. package/dist/testing/app_server.d.ts.map +1 -1
  78. package/dist/testing/app_server.js +4 -13
  79. package/dist/testing/attack_surface.d.ts +8 -7
  80. package/dist/testing/attack_surface.d.ts.map +1 -1
  81. package/dist/testing/attack_surface.js +12 -8
  82. package/dist/testing/audit_completeness.d.ts.map +1 -1
  83. package/dist/testing/audit_completeness.js +3 -5
  84. package/dist/testing/audit_drift_guard.d.ts +116 -0
  85. package/dist/testing/audit_drift_guard.d.ts.map +1 -0
  86. package/dist/testing/audit_drift_guard.js +134 -0
  87. package/dist/testing/connection_closer_helpers.d.ts +44 -0
  88. package/dist/testing/connection_closer_helpers.d.ts.map +1 -0
  89. package/dist/testing/connection_closer_helpers.js +48 -0
  90. package/dist/testing/integration.d.ts.map +1 -1
  91. package/dist/testing/integration.js +7 -9
  92. package/dist/testing/rate_limiting.js +4 -4
  93. package/dist/testing/rpc_helpers.d.ts +2 -1
  94. package/dist/testing/rpc_helpers.d.ts.map +1 -1
  95. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  96. package/dist/testing/rpc_round_trip.js +6 -8
  97. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  98. package/dist/testing/sse_round_trip.js +12 -6
  99. package/dist/testing/stubs.d.ts +11 -0
  100. package/dist/testing/stubs.d.ts.map +1 -1
  101. package/dist/testing/stubs.js +4 -0
  102. package/dist/testing/surface_invariants.d.ts +66 -1
  103. package/dist/testing/surface_invariants.d.ts.map +1 -1
  104. package/dist/testing/surface_invariants.js +103 -1
  105. package/dist/ui/SurfaceExplorer.svelte +161 -2
  106. package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
  107. package/package.json +1 -1
@@ -2,11 +2,14 @@
2
2
  * Bound audit-emit capability.
3
3
  *
4
4
  * `AuditEmitter` closes over the pool-level `Db`, the `on_audit_event`
5
- * subscriber chain, and the optional `AuditLogConfig` at backend-assembly
6
- * time. Consumers reach for `deps.audit.emit(ctx, input)` and never see the
7
- * pool handlers cannot accidentally emit an audit event against the
8
- * request's transactional `db` (which would be rolled back with the parent
9
- * on a handler throw).
5
+ * subscriber chain, and the optional `AuditLogConfig`. Built by the
6
+ * consumer's `audit_factory` callback on `CreateAppBackendOptions`
7
+ * `create_app_backend` invokes the factory once with its constructed
8
+ * `{db, log}` and lands the result on `AppDeps.audit`. Consumers reach
9
+ * for `deps.audit.emit(ctx, input)` and never see the pool — handlers
10
+ * cannot accidentally emit an audit event against the request's
11
+ * transactional `db` (which would be rolled back with the parent on a
12
+ * handler throw).
10
13
  *
11
14
  * Four methods cover every fan-out shape the auth domain needs:
12
15
  *
@@ -26,9 +29,13 @@
26
29
  * the query layer). Runs every listener on the chain; per-listener throws
27
30
  * are isolated.
28
31
  *
29
- * The chain is mutable so server assembly can append additional listeners
30
- * (e.g. the audit-log SSE registry composed by `create_app_server`) after
31
- * the backend is built but before the first request runs.
32
+ * The chain is a documented mutable seam `create_app_server` appends
33
+ * additional listeners after the backend is built (the factory-managed
34
+ * audit-log SSE, per-endpoint WS auth guards and logout closers, any
35
+ * `extra_audit_handlers` on a `WsEndpointSpec`) before the first request
36
+ * runs. Consumers can also append listeners directly on the emitter
37
+ * they return from `audit_factory` for setups that don't pass through
38
+ * `create_app_server`.
32
39
  *
33
40
  * @module
34
41
  */
@@ -126,12 +133,36 @@ export interface AuditEmitter {
126
133
  */
127
134
  notify(event: AuditLogEvent): void;
128
135
  /**
129
- * Mutable subscriber chain. Append at server assembly to compose the
130
- * factory-managed audit-log SSE on top of the consumer's
131
- * `on_audit_event` callback without shallow-copying `AppDeps`.
136
+ * Mutable subscriber chain. `create_app_server` appends the
137
+ * factory-managed audit-log SSE listener and per-endpoint WS auth
138
+ * guards / logout closers here so SSE + WS fan-out compose on top of
139
+ * the consumer's `on_audit_event` callback without shallow-copying
140
+ * `AppDeps`. Consumers can also append listeners directly for setups
141
+ * that don't run through `create_app_server`.
132
142
  */
133
143
  readonly on_event_chain: Array<(event: AuditLogEvent) => void>;
134
144
  }
145
+ /**
146
+ * Signature of `AuditEmitter.emit` — captured by the inner closure so
147
+ * `emit_role_grant_target` reaches the decorated function rather than
148
+ * a `this.emit` lookup. Exposed as a type so `EmitDecorator` can name
149
+ * the inner / outer slot.
150
+ */
151
+ export type AuditEmitFn = <T extends string>(ctx: AuditEmitterContext, input: AuditLogInput<T>) => void;
152
+ /**
153
+ * Wrap the bound `emit` before it gets captured by `emit_role_grant_target`'s
154
+ * closure and exposed on the returned `AuditEmitter`. Test instrumentation
155
+ * uses this to record `emit` invocation ordering against external markers
156
+ * (e.g. eager `ConnectionCloser` calls in `connection_closer.db.test.ts`)
157
+ * without paying the freeze-breaking footgun the pre-decorator
158
+ * `patch_audit_emit_capture` hot-patcher had.
159
+ *
160
+ * Because the inner closure captures the decorated function (not the
161
+ * outer slot reference), `emit_role_grant_target` also routes through
162
+ * the wrap — the close-vs-emit ordering helper sees role-grant-shape
163
+ * emissions, not just bare `emit` calls. Production never sets this.
164
+ */
165
+ export type EmitDecorator = (inner: AuditEmitFn) => AuditEmitFn;
135
166
  /** Options for `create_audit_emitter`. */
136
167
  export interface CreateAuditEmitterOptions {
137
168
  /** Pool-level `Db`. Captured by every emit call. */
@@ -149,9 +180,22 @@ export interface CreateAuditEmitterOptions {
149
180
  * registered here once at backend assembly.
150
181
  */
151
182
  audit_log_config?: AuditLogConfig;
183
+ /**
184
+ * Test-only hook to wrap `emit` at construction time. The decorated
185
+ * function is captured by `emit_role_grant_target`'s closure and is
186
+ * the function exposed on the returned `AuditEmitter`, so both call
187
+ * shapes route through it — see `EmitDecorator` for the rationale.
188
+ *
189
+ * Leave unset in production. The intended caller is
190
+ * `create_emit_ordering_audit_factory` in `testing/audit_drift_guard.ts`.
191
+ */
192
+ emit_decorator?: EmitDecorator;
152
193
  }
153
194
  /**
154
- * Build a bound `AuditEmitter`. Called once at `create_app_backend` time.
195
+ * Build a bound `AuditEmitter`. Typical caller is the consumer's
196
+ * `audit_factory` callback on `CreateAppBackendOptions` —
197
+ * `create_app_backend` invokes that callback with its constructed
198
+ * `{db, log}` and lands the result on `AppDeps.audit`.
155
199
  *
156
200
  * @param options - pool, logger, optional initial subscriber, optional config
157
201
  * @returns the bound emitter; closes over the pool + config + listener chain
@@ -1 +1 @@
1
- {"version":3,"file":"audit_emitter.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_emitter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,sBAAsB,CAAC;AAE9D,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,MAAM,uBAAuB,CAAC;AAE/B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,mBAAmB;IACnC,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,WAAW,yBAA0B,SAAQ,mBAAmB;IACrE,0FAA0F;IAC1F,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC5B;;;;;;;;;;;;;;;;OAgBG;IACH,IAAI,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,mBAAmB,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAChF;;;;;;;OAOG;IACH,sBAAsB,CAAC,CAAC,SAAS,MAAM,EACtC,GAAG,EAAE,yBAAyB,EAC9B,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE;QACN,UAAU,EAAE,CAAC,CAAC;QACd,iBAAiB,EAAE,IAAI,GAAG,IAAI,CAAC;QAC/B,eAAe,EAAE,IAAI,GAAG,IAAI,CAAC;QAC7B,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QACvC,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;KAChC,GACC,IAAI,CAAC;IACR;;;;;;;;;;OAUG;IACH,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE;;;;;;;OAOG;IACH,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC,CAAC;CAC/D;AAED,0CAA0C;AAC1C,MAAM,WAAW,yBAAyB;IACzC,oDAAoD;IACpD,EAAE,EAAE,EAAE,CAAC;IACP,qDAAqD;IACrD,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACzD;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,cAAc,CAAC;CAClC;AAED;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS,yBAAyB,KAAG,YAoDzE,CAAC"}
1
+ {"version":3,"file":"audit_emitter.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_emitter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,KAAK,EAAC,MAAM,EAAC,MAAM,yBAAyB,CAAC;AACpD,OAAO,KAAK,EAAC,IAAI,EAAC,MAAM,wBAAwB,CAAC;AAEjD,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,aAAa,CAAC;AACpC,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,sBAAsB,CAAC;AAE9D,OAAO,EAEN,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,aAAa,EAClB,MAAM,uBAAuB,CAAC;AAE/B;;;;;;;;;;;;;;;GAeG;AACH,MAAM,WAAW,mBAAmB;IACnC,eAAe,EAAE,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;CACtC;AAED;;;;GAIG;AACH,MAAM,WAAW,yBAA0B,SAAQ,mBAAmB;IACrE,0FAA0F;IAC1F,SAAS,EAAE,MAAM,CAAC;CAClB;AAED;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC5B;;;;;;;;;;;;;;;;OAgBG;IACH,IAAI,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,mBAAmB,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAChF;;;;;;;OAOG;IACH,sBAAsB,CAAC,CAAC,SAAS,MAAM,EACtC,GAAG,EAAE,yBAAyB,EAC9B,IAAI,EAAE,mBAAmB,EACzB,KAAK,EAAE;QACN,UAAU,EAAE,CAAC,CAAC;QACd,iBAAiB,EAAE,IAAI,GAAG,IAAI,CAAC;QAC/B,eAAe,EAAE,IAAI,GAAG,IAAI,CAAC;QAC7B,QAAQ,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QACvC,OAAO,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;KAChC,GACC,IAAI,CAAC;IACR;;;;;;;;;;OAUG;IACH,SAAS,CAAC,CAAC,SAAS,MAAM,EAAE,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE;;;;;;;OAOG;IACH,MAAM,CAAC,KAAK,EAAE,aAAa,GAAG,IAAI,CAAC;IACnC;;;;;;;OAOG;IACH,QAAQ,CAAC,cAAc,EAAE,KAAK,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC,CAAC;CAC/D;AAED;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,CAAC,CAAC,SAAS,MAAM,EAC1C,GAAG,EAAE,mBAAmB,EACxB,KAAK,EAAE,aAAa,CAAC,CAAC,CAAC,KACnB,IAAI,CAAC;AAEV;;;;;;;;;;;;GAYG;AACH,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,WAAW,KAAK,WAAW,CAAC;AAEhE,0CAA0C;AAC1C,MAAM,WAAW,yBAAyB;IACzC,oDAAoD;IACpD,EAAE,EAAE,EAAE,CAAC;IACP,qDAAqD;IACrD,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IACH,cAAc,CAAC,EAAE,CAAC,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC,GAAG,IAAI,CAAC;IACzD;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,cAAc,CAAC;IAClC;;;;;;;;OAQG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,oBAAoB,GAAI,SAAS,yBAAyB,KAAG,YAoEzE,CAAC"}
@@ -2,11 +2,14 @@
2
2
  * Bound audit-emit capability.
3
3
  *
4
4
  * `AuditEmitter` closes over the pool-level `Db`, the `on_audit_event`
5
- * subscriber chain, and the optional `AuditLogConfig` at backend-assembly
6
- * time. Consumers reach for `deps.audit.emit(ctx, input)` and never see the
7
- * pool handlers cannot accidentally emit an audit event against the
8
- * request's transactional `db` (which would be rolled back with the parent
9
- * on a handler throw).
5
+ * subscriber chain, and the optional `AuditLogConfig`. Built by the
6
+ * consumer's `audit_factory` callback on `CreateAppBackendOptions`
7
+ * `create_app_backend` invokes the factory once with its constructed
8
+ * `{db, log}` and lands the result on `AppDeps.audit`. Consumers reach
9
+ * for `deps.audit.emit(ctx, input)` and never see the pool — handlers
10
+ * cannot accidentally emit an audit event against the request's
11
+ * transactional `db` (which would be rolled back with the parent on a
12
+ * handler throw).
10
13
  *
11
14
  * Four methods cover every fan-out shape the auth domain needs:
12
15
  *
@@ -26,22 +29,29 @@
26
29
  * the query layer). Runs every listener on the chain; per-listener throws
27
30
  * are isolated.
28
31
  *
29
- * The chain is mutable so server assembly can append additional listeners
30
- * (e.g. the audit-log SSE registry composed by `create_app_server`) after
31
- * the backend is built but before the first request runs.
32
+ * The chain is a documented mutable seam `create_app_server` appends
33
+ * additional listeners after the backend is built (the factory-managed
34
+ * audit-log SSE, per-endpoint WS auth guards and logout closers, any
35
+ * `extra_audit_handlers` on a `WsEndpointSpec`) before the first request
36
+ * runs. Consumers can also append listeners directly on the emitter
37
+ * they return from `audit_factory` for setups that don't pass through
38
+ * `create_app_server`.
32
39
  *
33
40
  * @module
34
41
  */
35
42
  import { query_audit_log } from './audit_log_queries.js';
36
43
  import { builtin_audit_log_config, } from './audit_log_schema.js';
37
44
  /**
38
- * Build a bound `AuditEmitter`. Called once at `create_app_backend` time.
45
+ * Build a bound `AuditEmitter`. Typical caller is the consumer's
46
+ * `audit_factory` callback on `CreateAppBackendOptions` —
47
+ * `create_app_backend` invokes that callback with its constructed
48
+ * `{db, log}` and lands the result on `AppDeps.audit`.
39
49
  *
40
50
  * @param options - pool, logger, optional initial subscriber, optional config
41
51
  * @returns the bound emitter; closes over the pool + config + listener chain
42
52
  */
43
53
  export const create_audit_emitter = (options) => {
44
- const { db, log, audit_log_config = builtin_audit_log_config } = options;
54
+ const { db, log, audit_log_config = builtin_audit_log_config, emit_decorator } = options;
45
55
  const on_event_chain = [];
46
56
  if (options.on_audit_event)
47
57
  on_event_chain.push(options.on_audit_event);
@@ -64,9 +74,14 @@ export const create_audit_emitter = (options) => {
64
74
  log.error('Audit log write failed:', err);
65
75
  }
66
76
  };
67
- const emit = (ctx, input) => {
77
+ const base_emit = (ctx, input) => {
68
78
  ctx.pending_effects.push(emit_pool(input));
69
79
  };
80
+ // The decorated `emit` is what `emit_role_grant_target` captures below
81
+ // and what gets exposed on the returned object — both call shapes
82
+ // route through any `emit_decorator` the caller supplied. Production
83
+ // passes no decorator, so this collapses to `base_emit`.
84
+ const emit = emit_decorator ? emit_decorator(base_emit) : base_emit;
70
85
  const emit_role_grant_target = (ctx, auth, input) => {
71
86
  emit(ctx, {
72
87
  event_type: input.event_type,
@@ -79,5 +94,16 @@ export const create_audit_emitter = (options) => {
79
94
  metadata: input.metadata,
80
95
  });
81
96
  };
82
- return { emit, emit_role_grant_target, emit_pool, notify, on_event_chain };
97
+ // Freeze the slot layout so consumers cannot hot-patch `emit` /
98
+ // `emit_role_grant_target` / `emit_pool` / `notify` after construction.
99
+ // The previous test helper `patch_audit_emit_capture` did exactly this
100
+ // and only happened to work because the four slots were writable —
101
+ // `emit_role_grant_target` calls the closed-over inner `emit`, not
102
+ // `this.emit`, so the patch silently bypassed role-grant-shape emits.
103
+ // Tests that need instrumentation pass `emit_decorator` so the wrap
104
+ // is captured by the closure before the freeze (see
105
+ // `create_emit_ordering_audit_factory`). `on_event_chain` is a
106
+ // frozen reference but its array contents stay mutable —
107
+ // `create_app_server` appends to it post-assembly, by design.
108
+ return Object.freeze({ emit, emit_role_grant_target, emit_pool, notify, on_event_chain });
83
109
  };
@@ -336,9 +336,11 @@ export interface CreateAuditLogConfigOptions {
336
336
  * Throws when an `extra_events` key collides with a builtin event type, or
337
337
  * fails `AuditEventTypeName` format validation.
338
338
  *
339
- * Call once at startup; pass the result to `create_app_backend` (which
340
- * threads it into `AppDeps.audit`). Builtin handlers omit the
341
- * `audit_log_config` slot and pick up `builtin_audit_log_config`.
339
+ * Call once at startup; pass the result into the consumer's `audit_factory`
340
+ * body typically `({db, log}) => create_audit_emitter({db, log,
341
+ * audit_log_config, ...})` so it gets captured inside the bound
342
+ * `AppDeps.audit` emitter. Builtin handlers omit the `audit_log_config`
343
+ * slot and pick up `builtin_audit_log_config`.
342
344
  *
343
345
  * @throws Error when an `extra_events` key collides with a builtin event type or fails `AuditEventTypeName` format validation
344
346
  */
@@ -1 +1 @@
1
- {"version":3,"file":"audit_log_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"audit_log_schema.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/audit_log_schema.ts"],"names":[],"mappings":"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"}
@@ -291,9 +291,11 @@ export const builtin_audit_log_config = Object.freeze({
291
291
  * Throws when an `extra_events` key collides with a builtin event type, or
292
292
  * fails `AuditEventTypeName` format validation.
293
293
  *
294
- * Call once at startup; pass the result to `create_app_backend` (which
295
- * threads it into `AppDeps.audit`). Builtin handlers omit the
296
- * `audit_log_config` slot and pick up `builtin_audit_log_config`.
294
+ * Call once at startup; pass the result into the consumer's `audit_factory`
295
+ * body typically `({db, log}) => create_audit_emitter({db, log,
296
+ * audit_log_config, ...})` so it gets captured inside the bound
297
+ * `AppDeps.audit` emitter. Builtin handlers omit the `audit_log_config`
298
+ * slot and pick up `builtin_audit_log_config`.
297
299
  *
298
300
  * @throws Error when an `extra_events` key collides with a builtin event type or fails `AuditEventTypeName` format validation
299
301
  */
@@ -19,7 +19,7 @@ import type { StatResult } from '../runtime/deps.js';
19
19
  /** Input for `POST /bootstrap`. `token` is the one-shot token file contents. */
20
20
  export declare const BootstrapInput: z.ZodObject<{
21
21
  token: z.ZodString;
22
- username: z.ZodString;
22
+ username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
23
23
  password: z.ZodString;
24
24
  }, z.core.$strict>;
25
25
  export type BootstrapInput = z.infer<typeof BootstrapInput>;
@@ -23,7 +23,7 @@ export interface Invite {
23
23
  export declare const InviteJson: z.ZodObject<{
24
24
  id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
25
25
  email: z.ZodNullable<z.ZodEmail>;
26
- username: z.ZodNullable<z.ZodString>;
26
+ username: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
27
27
  claimed_by: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
28
28
  claimed_at: z.ZodNullable<z.ZodString>;
29
29
  created_at: z.ZodString;
@@ -34,7 +34,7 @@ export type InviteJson = z.infer<typeof InviteJson>;
34
34
  export declare const InviteWithUsernamesJson: z.ZodObject<{
35
35
  id: z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">;
36
36
  email: z.ZodNullable<z.ZodEmail>;
37
- username: z.ZodNullable<z.ZodString>;
37
+ username: z.ZodNullable<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>;
38
38
  claimed_by: z.ZodNullable<z.core.$ZodBranded<z.ZodUUID, "Uuid", "out">>;
39
39
  claimed_at: z.ZodNullable<z.ZodString>;
40
40
  created_at: z.ZodString;
@@ -24,7 +24,7 @@ export interface SignupRouteOptions extends AuthSessionRouteOptions {
24
24
  }
25
25
  /** Input for `POST /signup`. `email` is optional and must match any referenced invite. */
26
26
  export declare const SignupInput: z.ZodObject<{
27
- username: z.ZodString;
27
+ username: z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>;
28
28
  password: z.ZodString;
29
29
  email: z.ZodOptional<z.ZodEmail>;
30
30
  }, z.core.$strict>;
@@ -10,6 +10,7 @@
10
10
  * Option routing: shared `roles` flows to both admin and role-grant-offer;
11
11
  * `app_settings` goes to admin only; `default_ttl_ms` and `authorize` go
12
12
  * to role-grant-offer only; `max_tokens` goes to account only;
13
+ * shared `connection_closer` flows to admin + account (role-grant-offer ignores);
13
14
  * `notification_sender` reaches role-grant-offer transparently (admin + account
14
15
  * ignore it).
15
16
  *
@@ -1 +1 @@
1
- {"version":3,"file":"standard_rpc_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/standard_rpc_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAuB,KAAK,kBAAkB,EAAC,MAAM,oBAAoB,CAAC;AACjF,OAAO,EAEN,KAAK,2BAA2B,EAChC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAyB,KAAK,oBAAoB,EAAC,MAAM,sBAAsB,CAAC;AACvF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,qCAAqC,CAAC;AAC5E,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAExD;;;;;;;;GAQG;AACH,MAAM,WAAW,yBAChB,SAAQ,kBAAkB,EAAE,2BAA2B,EAAE,oBAAoB;CAAG;AAEjF;;;;;;;GAOG;AACH,MAAM,WAAW,sBAAuB,SAAQ,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,OAAO,CAAC;IACtF,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAChD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,sBAAsB,EAC5B,UAAS,yBAA8B,KACrC,KAAK,CAAC,SAAS,CAIjB,CAAC"}
1
+ {"version":3,"file":"standard_rpc_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/auth/standard_rpc_actions.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAAuB,KAAK,kBAAkB,EAAC,MAAM,oBAAoB,CAAC;AACjF,OAAO,EAEN,KAAK,2BAA2B,EAChC,MAAM,+BAA+B,CAAC;AACvC,OAAO,EAAyB,KAAK,oBAAoB,EAAC,MAAM,sBAAsB,CAAC;AACvF,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,WAAW,CAAC;AAChD,OAAO,KAAK,EAAC,kBAAkB,EAAC,MAAM,qCAAqC,CAAC;AAC5E,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAExD;;;;;;;;GAQG;AACH,MAAM,WAAW,yBAChB,SAAQ,kBAAkB,EAAE,2BAA2B,EAAE,oBAAoB;CAAG;AAEjF;;;;;;;GAOG;AACH,MAAM,WAAW,sBAAuB,SAAQ,IAAI,CAAC,gBAAgB,EAAE,KAAK,GAAG,OAAO,CAAC;IACtF,mBAAmB,CAAC,EAAE,kBAAkB,GAAG,IAAI,CAAC;CAChD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,2BAA2B,GACvC,MAAM,sBAAsB,EAC5B,UAAS,yBAA8B,KACrC,KAAK,CAAC,SAAS,CAIjB,CAAC"}
@@ -10,6 +10,7 @@
10
10
  * Option routing: shared `roles` flows to both admin and role-grant-offer;
11
11
  * `app_settings` goes to admin only; `default_ttl_ms` and `authorize` go
12
12
  * to role-grant-offer only; `max_tokens` goes to account only;
13
+ * shared `connection_closer` flows to admin + account (role-grant-offer ignores);
13
14
  * `notification_sender` reaches role-grant-offer transparently (admin + account
14
15
  * ignore it).
15
16
  *
@@ -24,7 +24,8 @@ see ../../docs/architecture.md.
24
24
  | `surface.ts` | `AppSurface`, `AppSurfaceSpec`, `generate_app_surface`, diagnostics |
25
25
  | `surface_query.ts` | Pure filters/groupings over `AppSurface` |
26
26
  | `proxy.ts` | Trusted-proxy middleware, CIDR parsing, rightmost-first XFF resolution |
27
- | `origin.ts` | Origin/Referer allowlist middleware with wildcard patterns |
27
+ | `ip_canonical.ts` | RFC 5952 IPv6 canonicalization + IPv4-mapped collapse; `IP_LITERAL_CHARS` regex |
28
+ | `origin.ts` | Origin allowlist middleware with wildcard patterns (Origin-only) |
28
29
  | `jsonrpc.ts` | JSON-RPC 2.0 envelope schemas (MCP superset), `JsonrpcErrorCode`, `_meta` |
29
30
  | `jsonrpc_errors.ts` | `ThrownJsonrpcError`, `jsonrpc_errors` throwers, HTTP-status mappings |
30
31
  | `jsonrpc_helpers.ts` | Message builders, type guards, input/result normalizers |
@@ -280,7 +281,7 @@ invariant 2's throw) was the empirical confirmation.
280
281
  - **Auth**: `ERROR_AUTHENTICATION_REQUIRED`, `ERROR_INSUFFICIENT_PERMISSIONS`,
281
282
  `ERROR_CREDENTIAL_TYPE_REQUIRED`, `ERROR_RATE_LIMIT_EXCEEDED`,
282
283
  `ERROR_INVALID_CREDENTIALS`, `ERROR_PAYLOAD_TOO_LARGE`
283
- - **Origin + bearer**: `ERROR_FORBIDDEN_ORIGIN`, `ERROR_FORBIDDEN_REFERER`,
284
+ - **Origin + bearer**: `ERROR_FORBIDDEN_ORIGIN`, `ERROR_FORBIDDEN_REFERER` (retained for consumer compat; no longer emitted),
284
285
  `ERROR_BEARER_REJECTED_BROWSER`, `ERROR_INVALID_TOKEN`, `ERROR_ACCOUNT_NOT_FOUND`
285
286
  - **Keeper/daemon**: `ERROR_INVALID_DAEMON_TOKEN`,
286
287
  `ERROR_KEEPER_ACCOUNT_NOT_CONFIGURED`, `ERROR_KEEPER_ACCOUNT_NOT_FOUND`
@@ -415,10 +416,15 @@ connection is from a configured trusted proxy. Without this middleware,
415
416
  Must run **before** auth and rate-limiting middleware. See the root
416
417
  ../../CLAUDE.md §Middleware Ordering.
417
418
 
418
- - `normalize_ip(ip)` — idempotent: lowercase + strip `::ffff:` prefix on
419
- IPv4-mapped IPv6 addresses; safe on non-IP strings (`'unknown'` → `'unknown'`).
420
- Subtle: only strips `::ffff:` when the suffix contains `.`, so pure
421
- IPv6 like `::ffff:1` is preserved
419
+ - `normalize_ip(ip)` — delegates to `canonicalize_ip` from `ip_canonical.ts`:
420
+ RFC 5952 IPv6 canonicalization (lowercase hex, longest-zero-run
421
+ compression), IPv4-mapped IPv6 emitted in dotted form and stripped of
422
+ the `::ffff:` prefix so the bucket collapses to plain IPv4. Idempotent;
423
+ safe on non-IP strings (`'unknown'` → `'unknown'`); strict char-set
424
+ filter (`IP_LITERAL_CHARS`) preserves malformed forms unchanged so
425
+ downstream `validate_ip_strict` can still reject them. Pure IPv6 like
426
+ `::ffff:1` (group[5]=0, not 0xffff — NOT IPv4-mapped) stays preserved.
427
+ Mirrors `zzz_server::proxy::normalize_ip`
422
428
  - `ProxyOptions` — `{trusted_proxies, get_connection_ip, log?}`
423
429
  - `ParsedProxy` — `{type: 'ip'; address}` or `{type: 'cidr'; network; prefix; address_type}`
424
430
  - `parse_proxy_entry(entry)` — accepts `'127.0.0.1'`, `'::1'`,
@@ -461,7 +467,7 @@ distinction in rate limiting and collapse to the proxy's connection
461
467
  IP (one bucket for everyone behind that proxy). nginx + cloud LBs
462
468
  don't include ports — bounded by operator configuration in practice.
463
469
 
464
- ### Origin/Referer allowlist — `origin.ts`
470
+ ### Origin allowlist — `origin.ts`
465
471
 
466
472
  Origin allowlisting for locally-running services — **not** the CSRF
467
473
  layer. CSRF is handled by `SameSite: strict` on session cookies (see
@@ -471,9 +477,19 @@ layer. CSRF is handled by `SameSite: strict` on session cookies (see
471
477
  - `should_allow_origin(origin, patterns)` — case-insensitive match
472
478
  - `verify_request_source(allowed_patterns)` — Hono handler:
473
479
  1. `Origin` header present → must match allowlist or 403 `ERROR_FORBIDDEN_ORIGIN`
474
- 2. No `Origin` + `Referer` present → extract origin, check, 403
475
- `ERROR_FORBIDDEN_REFERER` on mismatch
476
- 3. Neither header allow through (curl, CLI, token auth is primary control)
480
+ 2. No `Origin` allow through (curl, CLI, token auth is primary control)
481
+
482
+ **Origin-only by design.** Fetch spec mandates `Origin` on every unsafe
483
+ method, so a real browser request on any state-changing surface always
484
+ carries it. Non-browser clients (curl, server-to-server, CLI) don't
485
+ ship auto-attached session cookies, so CSRF isn't the relevant threat
486
+ there — auth (bearer / daemon token) is the actual control. A `Referer`
487
+ fallback would only widen the accepted-shape envelope without closing
488
+ a real CSRF hole. Mirrors `zzz_server::auth::is_request_origin_allowed`.
489
+
490
+ `ERROR_FORBIDDEN_REFERER` stays exported from `error_schemas.ts` for
491
+ consumers whose error-schema unions or test assertions still reference
492
+ it — the emit site is gone, the constant is not.
477
493
 
478
494
  Pattern syntax:
479
495
 
@@ -0,0 +1,99 @@
1
+ /**
2
+ * IP address canonicalization — collapse equivalent string forms into a
3
+ * single key per RFC 5952 (IPv6) plus the dotted form for IPv4-mapped
4
+ * IPv6 addresses.
5
+ *
6
+ * **Why this exists.** Without canonicalization, the four representations
7
+ * `::1`, `::01`, `::0001`, and `0:0:0:0:0:0:0:1` are the same IPv6 address
8
+ * but produce four distinct strings — so an attacker rotating
9
+ * equivalent forms behind a trusted-passthrough proxy could defeat
10
+ * per-IP rate limiting (each form gets a fresh bucket) and pollute
11
+ * `audit_log.ip` forensics. The collision can extend to IPv4-mapped
12
+ * IPv6 forms (`::ffff:127.0.0.1` vs `0:0:0:0:0:ffff:7f00:1` vs the
13
+ * bare `127.0.0.1`) — three keys for one address.
14
+ *
15
+ * Canonicalization runs through {@link canonicalize_ip} which:
16
+ *
17
+ * 1. Lowercases and char-set filters (`IP_LITERAL_CHARS`) — non-IP
18
+ * strings (`'unknown'`, `'attacker:controlled'`, `'::1\n'`) pass
19
+ * through unchanged so downstream strict validators can still
20
+ * reject them.
21
+ * 2. Parses via Hono's `convertIPv*ToBinary` family.
22
+ * 3. Re-emits the canonical RFC 5952 string (lowercase hex,
23
+ * longest-zero-run compressed, IPv4-mapped emitted in the dotted
24
+ * form mandated by RFC 5952 §5).
25
+ * 4. Strips the `::ffff:` prefix from dotted IPv4-mapped forms so the
26
+ * bucket collapses to plain IPv4 — the strip moves AFTER
27
+ * canonicalization because the dotted form is the only form the
28
+ * strip can recognize symmetrically.
29
+ *
30
+ * Mirrors `zzz_server::proxy::normalize_ip` (landed 2026-05-16) which
31
+ * uses the same parse-then-canonicalize-then-strip ordering for the
32
+ * same rate-limit-key-poisoning surface. See
33
+ * `~/dev/grimoire/lore/fuz_app/TODO_PROXY.md` §IPv6 String
34
+ * Canonicalization for the cross-backend parity record.
35
+ *
36
+ * @module
37
+ */
38
+ /**
39
+ * Allowed character set for a bare IP literal.
40
+ *
41
+ * Covers the union of IPv4 (digits + `.`), IPv6 (hex digits + `:`), and
42
+ * IPv4-mapped IPv6 forms (`::ffff:127.0.0.1`). Anything outside this
43
+ * set — brackets, whitespace, control bytes, letters g–z — disqualifies
44
+ * the input from parsing.
45
+ *
46
+ * Same regex `proxy.ts`'s `validate_ip_strict` uses; exported here so
47
+ * both modules can share one source of truth.
48
+ */
49
+ export declare const IP_LITERAL_CHARS: RegExp;
50
+ /**
51
+ * Canonicalize an IP address string.
52
+ *
53
+ * Returns the RFC 5952 canonical form for parseable IPv4 or IPv6
54
+ * input. Returns the input unchanged (only lowercased) when the input
55
+ * is non-IP (`'unknown'`), malformed (`'attacker:controlled'`,
56
+ * `'::1\n'`), or any string the strict char-set filter rejects.
57
+ *
58
+ * **Idempotent.** `canonicalize_ip(canonicalize_ip(x)) === canonicalize_ip(x)`
59
+ * for every input.
60
+ *
61
+ * **Order-safe for IPv4-mapped IPv6.** The `::ffff:` prefix strip
62
+ * runs AFTER the canonical emit because the canonical form of an
63
+ * IPv4-mapped IPv6 address is the dotted form (`::ffff:127.0.0.1`,
64
+ * not `::ffff:7f00:1`). Stripping before canonicalize would miss the
65
+ * full-hex form. Closes the
66
+ * `normalize_ipv4_mapped_collapse_is_order_safe` test from the Rust
67
+ * port.
68
+ *
69
+ * @example
70
+ * canonicalize_ip('::0001') // → '::1'
71
+ * canonicalize_ip('0:0:0:0:0:0:0:1') // → '::1'
72
+ * canonicalize_ip('2001:0DB8::0001') // → '2001:db8::1'
73
+ * canonicalize_ip('::ffff:127.0.0.1') // → '127.0.0.1'
74
+ * canonicalize_ip('0:0:0:0:0:ffff:7f00:1') // → '127.0.0.1'
75
+ * canonicalize_ip('::ffff:1') // → '::ffff:1' (NOT IPv4-mapped — group[5] is 0, not ffff)
76
+ * canonicalize_ip('127.0.0.1') // → '127.0.0.1'
77
+ * canonicalize_ip('not-an-ip') // → 'not-an-ip' (passes through)
78
+ * canonicalize_ip('::1\n') // → '::1\n' (fails char-set; passes through)
79
+ * canonicalize_ip('203.0.113.1:8080') // → '203.0.113.1:8080' (passes through; validate_ip_strict rejects)
80
+ */
81
+ export declare const canonicalize_ip: (ip: string) => string;
82
+ /**
83
+ * Convert a 128-bit IPv6 binary value into its RFC 5952 canonical string form.
84
+ *
85
+ * - IPv4-mapped (groups[0..5] = 0, groups[5] = 0xffff) emits the
86
+ * `::ffff:a.b.c.d` dotted form per RFC 5952 §5.
87
+ * - Otherwise: lowercase hex with no leading zeros per group (§4.1),
88
+ * the longest run of consecutive zero groups (≥ 2 groups) is
89
+ * replaced with `::` (§4.2.1, §4.2.3), and on equal-length runs the
90
+ * first one wins (§4.2.3). Single-zero groups stay as `0` (§4.2.2).
91
+ *
92
+ * Pure helper exported for the test suite to exercise the
93
+ * canonicalization invariants directly without a full
94
+ * `convertIPv6ToBinary` round-trip.
95
+ *
96
+ * @param bits - the 128-bit IPv6 value as `bigint` (only the low 128 bits are read)
97
+ */
98
+ export declare const ipv6_bigint_to_canonical: (bits: bigint) => string;
99
+ //# sourceMappingURL=ip_canonical.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ip_canonical.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/http/ip_canonical.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AAIH;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,QAAqB,CAAC;AAEnD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,eAAe,GAAI,IAAI,MAAM,KAAG,MAmC5C,CAAC;AAEF;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,wBAAwB,GAAI,MAAM,MAAM,KAAG,MA6DvD,CAAC"}