@fuzdev/fuz_app 0.148.0 → 0.150.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 (210) hide show
  1. package/dist/actions/CLAUDE.md +11 -9
  2. package/dist/actions/action_bridge.d.ts +7 -0
  3. package/dist/actions/action_bridge.d.ts.map +1 -1
  4. package/dist/actions/action_bridge.js +10 -0
  5. package/dist/actions/action_event.d.ts.map +1 -1
  6. package/dist/actions/action_event.js +14 -1
  7. package/dist/actions/action_event_types.d.ts +1 -1
  8. package/dist/actions/action_event_types.d.ts.map +1 -1
  9. package/dist/actions/perform_action.d.ts +17 -1
  10. package/dist/actions/perform_action.d.ts.map +1 -1
  11. package/dist/actions/perform_action.js +30 -7
  12. package/dist/actions/register_action_ws.d.ts +8 -2
  13. package/dist/actions/register_action_ws.d.ts.map +1 -1
  14. package/dist/actions/register_action_ws.js +24 -4
  15. package/dist/actions/transports_ws_auth_guard.d.ts +6 -4
  16. package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
  17. package/dist/actions/transports_ws_auth_guard.js +9 -6
  18. package/dist/actions/ws_endpoint_spec.d.ts +1 -1
  19. package/dist/auth/CLAUDE.md +72 -33
  20. package/dist/auth/account_action_specs.d.ts +12 -0
  21. package/dist/auth/account_action_specs.d.ts.map +1 -1
  22. package/dist/auth/account_action_specs.js +16 -2
  23. package/dist/auth/account_actions.d.ts.map +1 -1
  24. package/dist/auth/account_actions.js +7 -4
  25. package/dist/auth/actor_lookup_queries.d.ts +3 -2
  26. package/dist/auth/actor_lookup_queries.d.ts.map +1 -1
  27. package/dist/auth/actor_lookup_queries.js +6 -3
  28. package/dist/auth/actor_search_queries.d.ts +5 -2
  29. package/dist/auth/actor_search_queries.d.ts.map +1 -1
  30. package/dist/auth/actor_search_queries.js +11 -4
  31. package/dist/auth/admin_action_specs.d.ts +4 -4
  32. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  33. package/dist/auth/admin_action_specs.js +33 -12
  34. package/dist/auth/admin_actions.d.ts.map +1 -1
  35. package/dist/auth/admin_actions.js +35 -6
  36. package/dist/auth/all_cell_actions.d.ts +11 -2
  37. package/dist/auth/all_cell_actions.d.ts.map +1 -1
  38. package/dist/auth/all_cell_actions.js +2 -1
  39. package/dist/auth/audit_emitter.d.ts +23 -3
  40. package/dist/auth/audit_emitter.d.ts.map +1 -1
  41. package/dist/auth/audit_emitter.js +14 -4
  42. package/dist/auth/audit_log_schema.d.ts +1 -0
  43. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  44. package/dist/auth/audit_log_schema.js +9 -5
  45. package/dist/auth/bootstrap_account.d.ts +25 -1
  46. package/dist/auth/bootstrap_account.d.ts.map +1 -1
  47. package/dist/auth/bootstrap_account.js +37 -4
  48. package/dist/auth/bootstrap_routes.d.ts +3 -2
  49. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  50. package/dist/auth/bootstrap_routes.js +5 -4
  51. package/dist/auth/cell_action_specs.d.ts +215 -15
  52. package/dist/auth/cell_action_specs.d.ts.map +1 -1
  53. package/dist/auth/cell_action_specs.js +103 -16
  54. package/dist/auth/cell_actions.d.ts +40 -18
  55. package/dist/auth/cell_actions.d.ts.map +1 -1
  56. package/dist/auth/cell_actions.js +126 -19
  57. package/dist/auth/cell_audit_events.d.ts +1 -1
  58. package/dist/auth/cell_audit_events.d.ts.map +1 -1
  59. package/dist/auth/cell_audit_events.js +3 -2
  60. package/dist/auth/cell_audit_metadata.d.ts +11 -0
  61. package/dist/auth/cell_audit_metadata.d.ts.map +1 -1
  62. package/dist/auth/cell_audit_metadata.js +10 -0
  63. package/dist/auth/cell_grant_action_specs.d.ts +7 -0
  64. package/dist/auth/cell_grant_action_specs.d.ts.map +1 -1
  65. package/dist/auth/cell_grant_action_specs.js +7 -0
  66. package/dist/auth/cell_grant_actions.d.ts +36 -4
  67. package/dist/auth/cell_grant_actions.d.ts.map +1 -1
  68. package/dist/auth/cell_grant_actions.js +20 -3
  69. package/dist/auth/credential_type_schema.d.ts +3 -2
  70. package/dist/auth/credential_type_schema.d.ts.map +1 -1
  71. package/dist/auth/credential_type_schema.js +3 -2
  72. package/dist/auth/role_grant_offer_action_specs.d.ts +18 -2
  73. package/dist/auth/role_grant_offer_action_specs.d.ts.map +1 -1
  74. package/dist/auth/role_grant_offer_action_specs.js +30 -6
  75. package/dist/auth/role_grant_offer_actions.d.ts +9 -4
  76. package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
  77. package/dist/auth/role_grant_offer_actions.js +58 -7
  78. package/dist/auth/role_grant_offer_notifications.d.ts +3 -2
  79. package/dist/auth/role_grant_offer_notifications.d.ts.map +1 -1
  80. package/dist/auth/role_grant_offer_notifications.js +3 -2
  81. package/dist/auth/role_grant_offer_queries.d.ts +20 -0
  82. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
  83. package/dist/auth/role_grant_offer_queries.js +35 -0
  84. package/dist/auth/role_grant_queries.d.ts +27 -9
  85. package/dist/auth/role_grant_queries.d.ts.map +1 -1
  86. package/dist/auth/role_grant_queries.js +40 -13
  87. package/dist/auth/role_schema.d.ts +14 -8
  88. package/dist/auth/role_schema.d.ts.map +1 -1
  89. package/dist/auth/role_schema.js +6 -3
  90. package/dist/auth/self_service_role_actions.d.ts.map +1 -1
  91. package/dist/auth/self_service_role_actions.js +20 -0
  92. package/dist/db/CLAUDE.md +9 -2
  93. package/dist/db/cell_ddl.d.ts +9 -1
  94. package/dist/db/cell_ddl.d.ts.map +1 -1
  95. package/dist/db/cell_ddl.js +11 -1
  96. package/dist/db/cell_queries.d.ts +53 -7
  97. package/dist/db/cell_queries.d.ts.map +1 -1
  98. package/dist/db/cell_queries.js +61 -7
  99. package/dist/http/CLAUDE.md +14 -2
  100. package/dist/http/body_limit.d.ts +3 -1
  101. package/dist/http/body_limit.d.ts.map +1 -1
  102. package/dist/http/body_limit.js +6 -1
  103. package/dist/http/error_schemas.d.ts +8 -0
  104. package/dist/http/error_schemas.d.ts.map +1 -1
  105. package/dist/http/error_schemas.js +12 -0
  106. package/dist/http/jsonrpc.d.ts +9 -0
  107. package/dist/http/jsonrpc.d.ts.map +1 -1
  108. package/dist/http/jsonrpc.js +17 -0
  109. package/dist/http/jsonrpc_errors.d.ts +40 -15
  110. package/dist/http/jsonrpc_errors.d.ts.map +1 -1
  111. package/dist/http/jsonrpc_errors.js +99 -35
  112. package/dist/http/proxy.d.ts +4 -2
  113. package/dist/http/proxy.d.ts.map +1 -1
  114. package/dist/http/proxy.js +7 -2
  115. package/dist/http/surface.d.ts +3 -1
  116. package/dist/http/surface.d.ts.map +1 -1
  117. package/dist/http/surface.js +7 -4
  118. package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
  119. package/dist/realtime/sse_auth_guard.js +3 -2
  120. package/dist/schema_meta.d.ts +92 -1
  121. package/dist/schema_meta.d.ts.map +1 -1
  122. package/dist/schema_meta.js +117 -0
  123. package/dist/server/app_server.d.ts +13 -5
  124. package/dist/server/app_server.d.ts.map +1 -1
  125. package/dist/server/app_server.js +32 -8
  126. package/dist/testing/CLAUDE.md +196 -82
  127. package/dist/testing/adversarial_input_cases.d.ts +67 -7
  128. package/dist/testing/adversarial_input_cases.d.ts.map +1 -1
  129. package/dist/testing/adversarial_input_cases.js +210 -165
  130. package/dist/testing/app_server.d.ts +3 -1
  131. package/dist/testing/app_server.d.ts.map +1 -1
  132. package/dist/testing/audit_drift_guard.d.ts +3 -2
  133. package/dist/testing/audit_drift_guard.d.ts.map +1 -1
  134. package/dist/testing/audit_drift_guard.js +5 -3
  135. package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
  136. package/dist/testing/cross_backend/actor_lookup.js +27 -0
  137. package/dist/testing/cross_backend/audit_cross_helpers.d.ts +6 -0
  138. package/dist/testing/cross_backend/audit_cross_helpers.d.ts.map +1 -1
  139. package/dist/testing/cross_backend/audit_cross_helpers.js +11 -0
  140. package/dist/testing/cross_backend/capabilities.d.ts +29 -5
  141. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  142. package/dist/testing/cross_backend/capabilities.js +10 -6
  143. package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
  144. package/dist/testing/cross_backend/cell_crud.js +117 -2
  145. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  146. package/dist/testing/cross_backend/cell_gated_create.js +109 -13
  147. package/dist/testing/cross_backend/cell_moderate.d.ts +7 -1
  148. package/dist/testing/cross_backend/cell_moderate.d.ts.map +1 -1
  149. package/dist/testing/cross_backend/cell_moderate.js +68 -4
  150. package/dist/testing/cross_backend/cell_ref_admission.d.ts.map +1 -1
  151. package/dist/testing/cross_backend/cell_ref_admission.js +18 -9
  152. package/dist/testing/cross_backend/conformance_table.d.ts +5 -0
  153. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  154. package/dist/testing/cross_backend/conformance_table.js +12 -10
  155. package/dist/testing/cross_backend/cross_rpc.d.ts +3 -1
  156. package/dist/testing/cross_backend/cross_rpc.d.ts.map +1 -1
  157. package/dist/testing/cross_backend/cross_rpc.js +2 -2
  158. package/dist/testing/cross_backend/default_backend_configs.d.ts +9 -7
  159. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  160. package/dist/testing/cross_backend/default_backend_configs.js +3 -3
  161. package/dist/testing/cross_backend/differential.d.ts +7 -0
  162. package/dist/testing/cross_backend/differential.d.ts.map +1 -1
  163. package/dist/testing/cross_backend/differential.js +17 -0
  164. package/dist/testing/cross_backend/expected_schema.json +1 -0
  165. package/dist/testing/cross_backend/full_spine_mount.d.ts.map +1 -1
  166. package/dist/testing/cross_backend/full_spine_mount.js +5 -4
  167. package/dist/testing/cross_backend/role_grant_offer_lifecycle.d.ts.map +1 -1
  168. package/dist/testing/cross_backend/role_grant_offer_lifecycle.js +74 -2
  169. package/dist/testing/cross_backend/rpc_adversarial.d.ts +9 -0
  170. package/dist/testing/cross_backend/rpc_adversarial.d.ts.map +1 -1
  171. package/dist/testing/cross_backend/rpc_adversarial.js +16 -6
  172. package/dist/testing/cross_backend/rust_spine_consumer_surface.d.ts +1 -1
  173. package/dist/testing/cross_backend/rust_spine_stub_backend_config.d.ts.map +1 -1
  174. package/dist/testing/cross_backend/rust_spine_stub_backend_config.js +6 -3
  175. package/dist/testing/cross_backend/setup.d.ts +2 -2
  176. package/dist/testing/cross_backend/spine_suites.d.ts +85 -5
  177. package/dist/testing/cross_backend/spine_suites.d.ts.map +1 -1
  178. package/dist/testing/cross_backend/spine_suites.js +291 -20
  179. package/dist/testing/cross_backend/spine_surface_constants.d.ts +15 -0
  180. package/dist/testing/cross_backend/spine_surface_constants.d.ts.map +1 -1
  181. package/dist/testing/cross_backend/spine_surface_constants.js +24 -0
  182. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts +18 -19
  183. package/dist/testing/cross_backend/test_cell_gated_create_authorize.d.ts.map +1 -1
  184. package/dist/testing/cross_backend/test_cell_gated_create_authorize.js +66 -42
  185. package/dist/testing/cross_backend/testing_backdoor.d.ts +27 -4
  186. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  187. package/dist/testing/cross_backend/testing_backdoor.js +136 -41
  188. package/dist/testing/cross_backend/testing_reset_actions.d.ts +30 -15
  189. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  190. package/dist/testing/cross_backend/testing_reset_actions.js +52 -35
  191. package/dist/testing/cross_backend/transport_composition.js +3 -3
  192. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts.map +1 -1
  193. package/dist/testing/cross_backend/ts_spine_backend_config.js +6 -3
  194. package/dist/testing/rpc_attack_surface.d.ts +10 -0
  195. package/dist/testing/rpc_attack_surface.d.ts.map +1 -1
  196. package/dist/testing/rpc_attack_surface.js +15 -3
  197. package/dist/testing/rpc_envelope_cases.d.ts +5 -2
  198. package/dist/testing/rpc_envelope_cases.d.ts.map +1 -1
  199. package/dist/testing/rpc_envelope_cases.js +4 -1
  200. package/dist/testing/rpc_round_trip.js +3 -3
  201. package/dist/testing/schema_generators.d.ts +99 -2
  202. package/dist/testing/schema_generators.d.ts.map +1 -1
  203. package/dist/testing/schema_generators.js +244 -76
  204. package/dist/testing/stubs.d.ts +4 -3
  205. package/dist/testing/stubs.d.ts.map +1 -1
  206. package/dist/testing/stubs.js +6 -4
  207. package/dist/zod.d.ts +9 -0
  208. package/dist/zod.d.ts.map +1 -1
  209. package/dist/zod.js +20 -0
  210. package/package.json +1 -1
@@ -14,9 +14,10 @@ import './assert_dev_env.js';
14
14
  */
15
15
  import { z } from 'zod';
16
16
  import { zod_unwrap_to_object, zod_extract_fields } from '../zod.js';
17
+ import { resolve_invalid_reason } from '../schema_meta.js';
17
18
  import { is_null_schema } from '../http/schema_helpers.js';
18
19
  import { ERROR_INVALID_REQUEST_BODY, ERROR_INVALID_JSON_BODY } from '../http/error_schemas.js';
19
- import { detect_format, generate_valid_value } from './schema_generators.js';
20
+ import { detect_format, detect_json_schema_format, format_zod_issues, generate_valid_object, generate_valid_object_with_field, generate_valid_string_param, to_field_json_schema } from './schema_generators.js';
20
21
  // --- Payload generation ---
21
22
  /** One wrong-type value per base type — one representative is sufficient. */
22
23
  const wrong_type_for = (base_type) => {
@@ -55,7 +56,17 @@ const format_violation = (format) => {
55
56
  return null;
56
57
  }
57
58
  };
58
- // --- Input test case generation ---
59
+ /**
60
+ * `value` moved by `step`, up when that stays inside the field's numeric range
61
+ * and down otherwise — a number off the integer or `multipleOf` grid that
62
+ * still meets the range, so the grid check is the one a refusal names.
63
+ */
64
+ const off_grid_in_range = (json, value, step) => {
65
+ const up = value + step;
66
+ const over = (typeof json.maximum === 'number' && up > json.maximum) ||
67
+ (typeof json.exclusiveMaximum === 'number' && up >= json.exclusiveMaximum);
68
+ return over ? value - step : up;
69
+ };
59
70
  /**
60
71
  * Generate adversarial test cases for a route's input schema.
61
72
  *
@@ -65,12 +76,34 @@ const format_violation = (format) => {
65
76
  * - One wrong-type value per field
66
77
  * - Null for required non-nullable fields
67
78
  * - One format violation per constrained field
79
+ * - String length (empty, one under `minLength`, one over `maxLength`), numeric
80
+ * (out of range, non-integer, off a `multipleOf` grid) and array-length
81
+ * (empty, one under `minItems`, one over `maxItems`) boundaries
82
+ *
83
+ * Format and boundaries come from the field's input-side JSON Schema
84
+ * (`to_field_json_schema`), so a field carrying a `.transform()` or pipe and a
85
+ * `.nullable()` / `.nullish()` field get them too. A bound only a `.refine()`
86
+ * enforces generates nothing — JSON Schema can't express it.
68
87
  *
69
- * @throws Error if the seed body built by `generate_valid_value` fails
70
- * `input_schema.safeParse` — surfaces broken generation logic with the
71
- * Zod issues path rather than producing nonsense adversarial cases.
88
+ * Each per-field case is single-fault: valid except for the field it breaks.
89
+ * It changes one field of a valid base body — `options.base` when given, else
90
+ * one built by `generate_valid_object` — or, for a field that base omits, of
91
+ * the valid body `generate_valid_object_with_field` builds around it, so a case
92
+ * can't also break an object-level rule (adding `scope_kind` without its
93
+ * paired `scope_id`, or `slug` beside an exactly-one-of `id`). A field no valid
94
+ * body carries keeps its wrong-type and null cases on the base and gets no
95
+ * format or boundary cases, which would test no bound. A per-field case is
96
+ * emitted only when the field refuses its value, so no case is accidentally
97
+ * valid.
98
+ *
99
+ * @param input_schema - the input schema to attack
100
+ * @param options - a base body to seed the cases, and a label for errors
101
+ * @throws Error if the base body fails `input_schema.safeParse` — a seeded
102
+ * base the schema refuses, or a generated one that surfaces broken
103
+ * generation logic — naming the issues rather than producing nonsense
104
+ * adversarial cases.
72
105
  */
73
- export const generate_input_test_cases = (input_schema) => {
106
+ export const generate_input_test_cases = (input_schema, options) => {
74
107
  if (is_null_schema(input_schema))
75
108
  return [];
76
109
  const object_schema = zod_unwrap_to_object(input_schema);
@@ -78,33 +111,24 @@ export const generate_input_test_cases = (input_schema) => {
78
111
  return [];
79
112
  const fields = zod_extract_fields(object_schema);
80
113
  const cases = [];
81
- // build a base object with valid-ish values — must pass validation itself
82
- // skip optional fields without defaults to avoid generating invalid nested values
83
- const base = {};
84
- for (const field of fields) {
85
- if (!field.required && !field.has_default)
86
- continue;
87
- base[field.name] = generate_valid_value(field, object_schema.shape[field.name]);
88
- }
89
- let base_result = input_schema.safeParse(base);
90
- if (!base_result.success) {
91
- // Fallback for schemas with a top-level `.refine()` that requires at
92
- // least one of N optional fields. Fill optional fields until the base
93
- // satisfies validation; surfaces a clear error if no combination works.
94
- for (const field of fields) {
95
- if (field.required || field.has_default || field.name in base)
96
- continue;
97
- base[field.name] = generate_valid_value(field, object_schema.shape[field.name]);
98
- base_result = input_schema.safeParse(base);
99
- if (base_result.success)
100
- break;
114
+ const for_label = options?.label === undefined ? '' : ` for '${options.label}'`;
115
+ // the base object every case breaks one field of — must pass validation itself
116
+ let base;
117
+ if (options?.base) {
118
+ base = { ...options.base };
119
+ const base_result = input_schema.safeParse(base);
120
+ if (!base_result.success) {
121
+ throw new Error(`adversarial_input: the seeded base${for_label} fails validation — ` +
122
+ `fix the base for: ${format_zod_issues(base_result.error)}`);
101
123
  }
102
124
  }
103
- if (!base_result.success) {
104
- throw new Error(`adversarial_input: generated base object fails validation for schema — ` +
105
- `fix generate_valid_value for: ${base_result.error.issues
106
- .map((i) => `${i.path.join('.')}: ${i.message}`)
107
- .join(', ')}`);
125
+ else {
126
+ const generated = generate_valid_object(object_schema, input_schema);
127
+ if (!generated.result.success) {
128
+ throw new Error(`adversarial_input: generated base object fails validation${for_label} — ` +
129
+ `fix generate_valid_value for: ${format_zod_issues(generated.result.error)}`);
130
+ }
131
+ base = generated.value;
108
132
  }
109
133
  // whole-body structural: send array instead of object
110
134
  cases.push({
@@ -124,8 +148,33 @@ export const generate_input_test_cases = (input_schema) => {
124
148
  }
125
149
  for (const field of fields) {
126
150
  const field_schema = object_schema.shape[field.name];
151
+ // the valid body every case on this field changes only the field of: the
152
+ // base, or for a field the base omits, one that carries it — so the case
153
+ // can't also break an object-level rule (exactly one of, both or neither)
154
+ const field_base = generate_valid_object_with_field(object_schema, input_schema, base, field);
155
+ // every per-field case breaks the field, so the field itself must refuse
156
+ // its value — a candidate it accepts (a `z.coerce` field given a wrong
157
+ // type, a pattern that admits the violation string) is no invalid case.
158
+ // A type case (wrong type, null) on a field no valid body carries falls
159
+ // back to the base; a value case — a format or a bound — is built only on
160
+ // a body that is valid but for the field, or it tests no bound at all
161
+ const push_field_case = (label, value, kind) => {
162
+ if (field_schema.safeParse(value).success)
163
+ return;
164
+ const body_base = field_base ?? (kind === 'type' ? base : null);
165
+ if (!body_base)
166
+ return;
167
+ cases.push({
168
+ label,
169
+ body: { ...body_base, [field.name]: value },
170
+ field: field.name,
171
+ expected_error: ERROR_INVALID_REQUEST_BODY
172
+ });
173
+ };
174
+ // the field's valid value the value cases start from
175
+ const valid_value = field_base?.[field.name];
127
176
  // missing required field (skip fields with defaults — Zod fills them)
128
- if (field.required && !field.has_default) {
177
+ if (field.required && !field.has_default && !field_schema.safeParse(undefined).success) {
129
178
  const without = {};
130
179
  for (const [k, v] of Object.entries(base)) {
131
180
  if (k !== field.name)
@@ -134,159 +183,162 @@ export const generate_input_test_cases = (input_schema) => {
134
183
  cases.push({
135
184
  label: `missing: ${field.name}`,
136
185
  body: without,
186
+ field: field.name,
137
187
  expected_error: ERROR_INVALID_REQUEST_BODY
138
188
  });
139
189
  }
140
190
  // one wrong-type value
141
191
  const wrong = wrong_type_for(field.base_type);
142
- if (wrong) {
143
- cases.push({
144
- label: `wrong type: ${field.name} (${wrong.label})`,
145
- body: { ...base, [field.name]: wrong.value },
146
- expected_error: ERROR_INVALID_REQUEST_BODY
147
- });
148
- }
192
+ if (wrong)
193
+ push_field_case(`wrong type: ${field.name} (${wrong.label})`, wrong.value, 'type');
149
194
  // null for required non-nullable fields
150
195
  if (field.required && !field.nullable && field.base_type !== 'null') {
151
- cases.push({
152
- label: `null: ${field.name}`,
153
- body: { ...base, [field.name]: null },
154
- expected_error: ERROR_INVALID_REQUEST_BODY
155
- });
196
+ push_field_case(`null: ${field.name}`, null, 'type');
156
197
  }
198
+ // the field's input-side JSON Schema — format, pattern and bounds, read
199
+ // through a transform or pipe and under a nullable's `anyOf`
200
+ const json = to_field_json_schema(field_schema);
201
+ if (!json || !field_base)
202
+ continue; // no constraints, or no body to break them on
157
203
  // format violation for constrained string fields
158
- const format = detect_format(field_schema);
204
+ const format = detect_json_schema_format(json);
159
205
  if (format) {
160
206
  const violation = format_violation(format);
161
207
  if (violation) {
162
- cases.push({
163
- label: `format: ${field.name} (${violation.label})`,
164
- body: { ...base, [field.name]: violation.value },
165
- expected_error: ERROR_INVALID_REQUEST_BODY
166
- });
208
+ push_field_case(`format: ${field.name} (${violation.label})`, violation.value, 'value');
209
+ }
210
+ }
211
+ // string length boundaries — `x`s are one UTF-16 unit and one code point
212
+ // each, so the over-`maxLength` string is over in either count
213
+ if (field.base_type === 'string' || field.base_type === 'uuid' || field.base_type === 'email') {
214
+ if (typeof json.minLength === 'number' && json.minLength > 0) {
215
+ push_field_case(`empty string: ${field.name}`, '', 'value');
216
+ }
217
+ // one short of the minimum, distinct from the empty string — a prefix of
218
+ // the valid value where there is one, so a pattern it met still holds
219
+ if (typeof json.minLength === 'number' && json.minLength > 1) {
220
+ const under = json.minLength - 1;
221
+ push_field_case(`under minLength: ${field.name} (${under} chars)`, typeof valid_value === 'string' && valid_value.length >= under
222
+ ? valid_value.slice(0, under)
223
+ : 'x'.repeat(under), 'value');
224
+ }
225
+ if (typeof json.maxLength === 'number') {
226
+ push_field_case(`over maxLength: ${field.name} (${json.maxLength + 1} chars)`, 'x'.repeat(json.maxLength + 1), 'value');
167
227
  }
168
228
  }
169
- // --- Boundary cases via JSON Schema introspection ---
170
- try {
171
- const json = z.toJSONSchema(field_schema);
172
- // string length boundaries
173
- if (field.base_type === 'string' ||
174
- field.base_type === 'uuid' ||
175
- field.base_type === 'email') {
176
- if (typeof json.minLength === 'number' && json.minLength > 0) {
177
- cases.push({
178
- label: `empty string: ${field.name}`,
179
- body: { ...base, [field.name]: '' },
180
- expected_error: ERROR_INVALID_REQUEST_BODY
181
- });
182
- }
183
- if (typeof json.maxLength === 'number') {
184
- cases.push({
185
- label: `over maxLength: ${field.name} (${json.maxLength + 1} chars)`,
186
- body: { ...base, [field.name]: 'x'.repeat(json.maxLength + 1) },
187
- expected_error: ERROR_INVALID_REQUEST_BODY
188
- });
189
- }
229
+ // numeric boundaries
230
+ if (field.base_type === 'number' || field.base_type === 'int') {
231
+ if (typeof json.minimum === 'number') {
232
+ push_field_case(`below minimum: ${field.name} (${json.minimum - 1})`, json.minimum - 1, 'value');
233
+ }
234
+ if (typeof json.maximum === 'number') {
235
+ push_field_case(`above maximum: ${field.name} (${json.maximum + 1})`, json.maximum + 1, 'value');
236
+ }
237
+ if (typeof json.exclusiveMinimum === 'number') {
238
+ push_field_case(`at exclusive minimum: ${field.name} (${json.exclusiveMinimum})`, json.exclusiveMinimum, 'value');
239
+ }
240
+ if (typeof json.exclusiveMaximum === 'number') {
241
+ push_field_case(`at exclusive maximum: ${field.name} (${json.exclusiveMaximum})`, json.exclusiveMaximum, 'value');
242
+ }
243
+ // 0 and negative for positive-only fields
244
+ if (typeof json.minimum === 'number' && json.minimum > 0) {
245
+ push_field_case(`zero for positive-only: ${field.name}`, 0, 'value');
246
+ push_field_case(`negative for positive-only: ${field.name}`, -1, 'value');
190
247
  }
191
- // numeric boundaries
192
- if (field.base_type === 'number' || field.base_type === 'int') {
193
- if (typeof json.minimum === 'number') {
194
- cases.push({
195
- label: `below minimum: ${field.name} (${json.minimum - 1})`,
196
- body: { ...base, [field.name]: json.minimum - 1 },
197
- expected_error: ERROR_INVALID_REQUEST_BODY
198
- });
199
- }
200
- if (typeof json.maximum === 'number') {
201
- cases.push({
202
- label: `above maximum: ${field.name} (${json.maximum + 1})`,
203
- body: { ...base, [field.name]: json.maximum + 1 },
204
- expected_error: ERROR_INVALID_REQUEST_BODY
205
- });
206
- }
207
- if (typeof json.exclusiveMinimum === 'number') {
208
- cases.push({
209
- label: `at exclusive minimum: ${field.name} (${json.exclusiveMinimum})`,
210
- body: { ...base, [field.name]: json.exclusiveMinimum },
211
- expected_error: ERROR_INVALID_REQUEST_BODY
212
- });
213
- }
214
- if (typeof json.exclusiveMaximum === 'number') {
215
- cases.push({
216
- label: `at exclusive maximum: ${field.name} (${json.exclusiveMaximum})`,
217
- body: { ...base, [field.name]: json.exclusiveMaximum },
218
- expected_error: ERROR_INVALID_REQUEST_BODY
219
- });
220
- }
221
- // 0 and negative for positive-only fields
222
- if (typeof json.minimum === 'number' && json.minimum > 0) {
223
- cases.push({
224
- label: `zero for positive-only: ${field.name}`,
225
- body: { ...base, [field.name]: 0 },
226
- expected_error: ERROR_INVALID_REQUEST_BODY
227
- });
228
- cases.push({
229
- label: `negative for positive-only: ${field.name}`,
230
- body: { ...base, [field.name]: -1 },
231
- expected_error: ERROR_INVALID_REQUEST_BODY
232
- });
233
- }
248
+ // off the grid by half a step from the valid value, inside the range,
249
+ // so the integer or `multipleOf` check is the one that refuses
250
+ const valid_number = typeof valid_value === 'number' ? valid_value : 1;
251
+ if (json.type === 'integer') {
252
+ const value = off_grid_in_range(json, valid_number, 0.5);
253
+ push_field_case(`non-integer: ${field.name} (${value})`, value, 'value');
234
254
  }
235
- // array length boundaries
236
- if (field.base_type === 'array') {
237
- if (typeof json.minItems === 'number' && json.minItems > 0) {
238
- cases.push({
239
- label: `empty array for minItems > 0: ${field.name}`,
240
- body: { ...base, [field.name]: [] },
241
- expected_error: ERROR_INVALID_REQUEST_BODY
242
- });
243
- }
244
- if (typeof json.maxItems === 'number') {
245
- // generate an array one item over the max (items are null — schema-agnostic)
246
- cases.push({
247
- label: `over maxItems: ${field.name} (${json.maxItems + 1} items)`,
248
- body: { ...base, [field.name]: Array.from({ length: json.maxItems + 1 }, () => null) },
249
- expected_error: ERROR_INVALID_REQUEST_BODY
250
- });
251
- }
255
+ if (typeof json.multipleOf === 'number' && json.multipleOf > 0) {
256
+ // a whole step for an integer field, so the integer check still holds
257
+ const step = json.type === 'integer' && json.multipleOf > 1 ? 1 : json.multipleOf / 2;
258
+ const value = off_grid_in_range(json, valid_number, step);
259
+ push_field_case(`not multipleOf: ${field.name} (${value}, multipleOf ${json.multipleOf})`, value, 'value');
252
260
  }
253
261
  }
254
- catch {
255
- // schema can't be converted to JSON Schema, skip boundary cases
262
+ // array length boundaries
263
+ if (field.base_type === 'array') {
264
+ if (typeof json.minItems === 'number' && json.minItems > 0) {
265
+ push_field_case(`empty array for minItems > 0: ${field.name}`, [], 'value');
266
+ }
267
+ // one short of the minimum, distinct from the empty array — the valid
268
+ // value's own items, so only the count is wrong
269
+ if (typeof json.minItems === 'number' &&
270
+ json.minItems > 1 &&
271
+ Array.isArray(valid_value) &&
272
+ valid_value.length >= json.minItems - 1) {
273
+ push_field_case(`under minItems: ${field.name} (${json.minItems - 1} items)`, valid_value.slice(0, json.minItems - 1), 'value');
274
+ }
275
+ if (typeof json.maxItems === 'number') {
276
+ // one item over the max (items are null — schema-agnostic)
277
+ push_field_case(`over maxItems: ${field.name} (${json.maxItems + 1} items)`, Array.from({ length: json.maxItems + 1 }, () => null), 'value');
278
+ }
256
279
  }
257
280
  }
258
281
  return cases;
259
282
  };
260
- // --- Params test case generation ---
283
+ /**
284
+ * The `error.data.reason` an RPC refusal of `test_case` owes: the declared
285
+ * `invalid_reason` of the field it breaks, resolved as the dispatcher resolves
286
+ * a failure on that field (`resolve_invalid_reason`), or `undefined` — no
287
+ * reason — for an undeclared field or a whole-body case.
288
+ *
289
+ * @param input_schema - the input schema the case was generated from
290
+ * @param test_case - a case `generate_input_test_cases` produced from it
291
+ * @returns the owed reason, or `undefined` when the refusal carries none
292
+ */
293
+ export const expected_invalid_reason = (input_schema, test_case) => test_case.field === undefined
294
+ ? undefined
295
+ : resolve_invalid_reason(input_schema, [test_case.field]);
296
+ // --- Params and query test case generation ---
297
+ /**
298
+ * A valid string record for a route's params or query schema — each field the
299
+ * string `generate_valid_string_param` finds for it — checked against the
300
+ * whole schema, so a case changing one field breaks that field only.
301
+ *
302
+ * @throws Error if a field has no string it accepts, or the record fails the
303
+ * schema, naming the issues
304
+ */
305
+ const generate_string_record_base = (schema, kind) => {
306
+ const base = {};
307
+ for (const field of zod_extract_fields(schema)) {
308
+ const value = generate_valid_string_param(field, schema.shape[field.name]);
309
+ if (value === null) {
310
+ throw new Error(`adversarial_input: no generated ${kind} value passes field '${field.name}'`);
311
+ }
312
+ base[field.name] = value;
313
+ }
314
+ const result = schema.safeParse(base);
315
+ if (!result.success) {
316
+ throw new Error(`adversarial_input: generated ${kind} base fails validation — ` +
317
+ `fix generate_valid_string_param for: ${format_zod_issues(result.error)}`);
318
+ }
319
+ return base;
320
+ };
261
321
  /**
262
322
  * Generate adversarial test cases for a route's params schema.
263
323
  *
264
324
  * Params are always strings from URL segments. Only generates cases for
265
325
  * format-constrained fields (uuid, pattern) since unconstrained string
266
- * params accept any string value.
326
+ * params accept any string value, and only a violation the field refuses.
327
+ * Each case breaks one field of a valid params record.
328
+ *
329
+ * @throws Error if no valid params record can be generated
267
330
  */
268
331
  export const generate_params_test_cases = (params_schema) => {
269
332
  const fields = zod_extract_fields(params_schema);
270
333
  const cases = [];
271
- // build base params with valid-ish values
272
- const base_params = {};
273
- for (const field of fields) {
274
- const field_schema = params_schema.shape[field.name];
275
- const format = detect_format(field_schema);
276
- if (format === 'uuid' || field.base_type === 'uuid') {
277
- base_params[field.name] = '00000000-0000-0000-0000-000000000000';
278
- }
279
- else {
280
- base_params[field.name] = 'test_value';
281
- }
282
- }
334
+ const base_params = generate_string_record_base(params_schema, 'params');
283
335
  for (const field of fields) {
284
336
  const field_schema = params_schema.shape[field.name];
285
337
  const format = detect_format(field_schema);
286
338
  if (!format)
287
339
  continue; // unconstrained string — any value passes
288
340
  const violation = format_violation(format);
289
- if (violation) {
341
+ if (violation && !field_schema.safeParse(violation.value).success) {
290
342
  cases.push({
291
343
  label: `${violation.label}: param ${field.name}`,
292
344
  params: { ...base_params, [field.name]: violation.value }
@@ -295,32 +347,26 @@ export const generate_params_test_cases = (params_schema) => {
295
347
  }
296
348
  return cases;
297
349
  };
298
- // --- Query test case generation ---
299
350
  /**
300
351
  * Generate adversarial test cases for a route's query schema.
301
352
  *
302
353
  * Query params are always strings from the URL. Generates cases for:
303
- * - Missing required fields
304
- * - Format violations on constrained fields (uuid, pattern)
354
+ * - Missing required fields the field refuses to go without (a preprocessed
355
+ * field with a default accepts its absence)
356
+ * - Format violations on constrained fields (uuid, pattern) the field refuses
357
+ *
358
+ * Each case breaks one field of a valid query record.
359
+ *
360
+ * @throws Error if no valid query record can be generated
305
361
  */
306
362
  export const generate_query_test_cases = (query_schema) => {
307
363
  const fields = zod_extract_fields(query_schema);
308
364
  const cases = [];
309
- // build base query with valid-ish values
310
- const base_query = {};
365
+ const base_query = generate_string_record_base(query_schema, 'query');
311
366
  for (const field of fields) {
312
367
  const field_schema = query_schema.shape[field.name];
313
- const format = detect_format(field_schema);
314
- if (format === 'uuid' || field.base_type === 'uuid') {
315
- base_query[field.name] = '00000000-0000-0000-0000-000000000000';
316
- }
317
- else {
318
- base_query[field.name] = 'test_value';
319
- }
320
- }
321
- for (const field of fields) {
322
368
  // missing required field
323
- if (field.required && !field.has_default) {
369
+ if (field.required && !field.has_default && !field_schema.safeParse(undefined).success) {
324
370
  const without = {};
325
371
  for (const [k, v] of Object.entries(base_query)) {
326
372
  if (k !== field.name)
@@ -332,12 +378,11 @@ export const generate_query_test_cases = (query_schema) => {
332
378
  });
333
379
  }
334
380
  // format violation for constrained string fields
335
- const field_schema = query_schema.shape[field.name];
336
381
  const format = detect_format(field_schema);
337
382
  if (!format)
338
383
  continue;
339
384
  const violation = format_violation(format);
340
- if (violation) {
385
+ if (violation && !field_schema.safeParse(violation.value).success) {
341
386
  cases.push({
342
387
  label: `${violation.label}: query ${field.name}`,
343
388
  query: { ...base_query, [field.name]: violation.value }
@@ -371,7 +371,9 @@ export interface CreateTestAppForBootstrapOptions {
371
371
  /**
372
372
  * Token contents the stub fs returns when reading `bootstrap.token_path`.
373
373
  * The test posts a body containing this same value as `token` to satisfy
374
- * the timing-safe equality check inside `bootstrap_account`.
374
+ * the timing-safe equality check inside `bootstrap_account`. At least
375
+ * `BOOTSTRAP_TOKEN_MIN_BYTES` long, or the read refuses it as a missing
376
+ * file.
375
377
  */
376
378
  bootstrap_token: string;
377
379
  db?: Db;
@@ -1 +1 @@
1
- {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"app_server.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/app_server.ts"],"names":[],"mappings":"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"}
@@ -61,8 +61,9 @@ export interface RecordingAuditEmitter {
61
61
  * `notify` is a no-op; `add_listener` records into a local array that
62
62
  * `listener_count` reports, and returns a remover with the production
63
63
  * semantics (per registration, idempotent) — registered listeners never fire,
64
- * since this emitter captures `emit` shapes, not fan-out. `drain_inflight` resolves at once:
65
- * a recorded call writes nothing, so nothing is ever in flight.
64
+ * since this emitter captures `emit` shapes, not fan-out. `drain_inflight` resolves at once
65
+ * and `is_tracking_inflight` answers `false`: a recorded call writes nothing, so nothing is
66
+ * ever in flight.
66
67
  *
67
68
  * `emit` AND `emit_pool` both append to `calls`, so a test reads either
68
69
  * entry point's writes off the same array.
@@ -1 +1 @@
1
- {"version":3,"file":"audit_drift_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_drift_guard.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAW7B,OAAO,EAGN,KAAK,YAAY,EACjB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAGN,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,MAAM,6BAA6B,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,yBAAyB,QAAO,IAO5C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,QAAO,IAgBxC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,eAAO,MAAM,8BAA8B,GAC1C,YAAY,KAAK,CAAC,aAAa,CAAC,KAC9B,qBAqCF,CAAC;AA2BF,iDAAiD;AACjD,MAAM,WAAW,+BAA+B;IAC/C;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CACvC;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,2BAA2B,GACvC,OAAO,IAAI,CAAC,iBAAiB,EAAE,YAAY,GAAG,SAAS,GAAG,UAAU,CAAC,EACrE,UAAU,+BAA+B,KACvC,IAcF,CAAC"}
1
+ {"version":3,"file":"audit_drift_guard.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_drift_guard.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAW7B,OAAO,EAGN,KAAK,YAAY,EACjB,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAGN,KAAK,iBAAiB,EACtB,KAAK,aAAa,EAClB,MAAM,6BAA6B,CAAC;AAErC;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,eAAO,MAAM,yBAAyB,QAAO,IAO5C,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,QAAO,IAgBxC,CAAC;AAEF;;;;;;GAMG;AACH,MAAM,WAAW,qBAAqB;IACrC,OAAO,EAAE,YAAY,CAAC;IACtB,KAAK,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,eAAO,MAAM,8BAA8B,GAC1C,YAAY,KAAK,CAAC,aAAa,CAAC,KAC9B,qBAsCF,CAAC;AA2BF,iDAAiD;AACjD,MAAM,WAAW,+BAA+B;IAC/C;;;;OAIG;IACH,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC;CACvC;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,2BAA2B,GACvC,OAAO,IAAI,CAAC,iBAAiB,EAAE,YAAY,GAAG,SAAS,GAAG,UAAU,CAAC,EACrE,UAAU,+BAA+B,KACvC,IAcF,CAAC"}
@@ -64,8 +64,9 @@ export const assert_no_audit_drift = () => {
64
64
  * `notify` is a no-op; `add_listener` records into a local array that
65
65
  * `listener_count` reports, and returns a remover with the production
66
66
  * semantics (per registration, idempotent) — registered listeners never fire,
67
- * since this emitter captures `emit` shapes, not fan-out. `drain_inflight` resolves at once:
68
- * a recorded call writes nothing, so nothing is ever in flight.
67
+ * since this emitter captures `emit` shapes, not fan-out. `drain_inflight` resolves at once
68
+ * and `is_tracking_inflight` answers `false`: a recorded call writes nothing, so nothing is
69
+ * ever in flight.
69
70
  *
70
71
  * `emit` AND `emit_pool` both append to `calls`, so a test reads either
71
72
  * entry point's writes off the same array.
@@ -118,7 +119,8 @@ export const create_recording_audit_emitter = (calls_ref) => {
118
119
  };
119
120
  },
120
121
  listener_count: () => listeners.length,
121
- drain_inflight: () => Promise.resolve()
122
+ drain_inflight: () => Promise.resolve(),
123
+ is_tracking_inflight: () => false
122
124
  };
123
125
  return { emitter, calls };
124
126
  };
@@ -1 +1 @@
1
- {"version":3,"file":"actor_lookup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/actor_lookup.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAkC9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG3D;;;;;GAKG;AACH,MAAM,MAAM,2BAA2B,GAAG,wBAAwB,CAAC;AASnE,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IA8BxF,CAAC"}
1
+ {"version":3,"file":"actor_lookup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/actor_lookup.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAoC9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG3D;;;;;GAKG;AACH,MAAM,MAAM,2BAA2B,GAAG,wBAAwB,CAAC;AASnE,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IA4DxF,CAAC"}
@@ -18,6 +18,8 @@ import '../assert_dev_env.js';
18
18
  * `account_id` / `email` / timestamp / role field (the wire shape's
19
19
  * deliberate info-leak posture). This is the assertion that exercises the
20
20
  * Rust row→JSON mapping against the TS canonical shape.
21
+ * - **a soft-deleted account's actor doesn't resolve** — beside a live one,
22
+ * after the keeper's `account_delete`.
21
23
  *
22
24
  * Runs both legs via the shared `{setup_test}` protocol: the in-process leg
23
25
  * (`auth/actor_lookup_parity.db.test.ts`, plain `gro test`) and the
@@ -58,5 +60,30 @@ export const describe_actor_lookup_cross_tests = (options) => {
58
60
  assert(!(key in row), `actor_lookup row must not leak '${key}'`);
59
61
  }
60
62
  });
63
+ test("a soft-deleted account's actor no longer resolves", async () => {
64
+ const fixture = await setup_test();
65
+ const gone = await fixture.create_account({ username: 'lookup_soft_deleted' });
66
+ const live = await fixture.create_account({ username: 'lookup_live' });
67
+ const deleted = await fixture.transport(rpc_path, {
68
+ method: 'POST',
69
+ headers: { ...fixture.create_session_headers(), 'content-type': 'application/json' },
70
+ body: JSON.stringify({
71
+ jsonrpc: '2.0',
72
+ method: 'account_delete',
73
+ params: { account_id: gone.account.id },
74
+ id: 'delete'
75
+ })
76
+ });
77
+ assert.strictEqual(deleted.status, 200, 'the admin deletes the account');
78
+ await deleted.text();
79
+ const res = await fixture.transport(rpc_path, {
80
+ method: 'POST',
81
+ headers: { ...fixture.create_session_headers(), 'content-type': 'application/json' },
82
+ body: lookup_envelope([gone.actor.id, live.actor.id], 'tombstone-lookup')
83
+ });
84
+ assert.strictEqual(res.status, 200);
85
+ const body = (await res.json());
86
+ assert.deepStrictEqual(body.result?.actors?.map((a) => a.id), [live.actor.id], 'the tombstoned actor drops out, the live one resolves');
87
+ });
61
88
  });
62
89
  };
@@ -18,6 +18,12 @@ export interface AuditReadFixture {
18
18
  * backend must mount the action — every spine test binary does; in-process
19
19
  * suites mount `create_testing_drain_effects_action`.
20
20
  *
21
+ * Refuses an answer without `tracked: true`. A binary whose audit emitter was
22
+ * built without in-flight tracking answers the drain at once, waiting on
23
+ * nothing, and its audit reads race the writes — an absent row then reads as
24
+ * a pass. Every audit read runs through here, so each one checks the barrier
25
+ * is live.
26
+ *
21
27
  * @param fixture - the per-test fixture, whose daemon token authorizes the drain
22
28
  * @param rpc_path - the RPC endpoint the drain action is mounted on
23
29
  */