@fuzdev/fuz_app 0.110.1 → 0.112.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 +15 -11
  2. package/dist/actions/action_rpc.d.ts +3 -3
  3. package/dist/actions/action_rpc.d.ts.map +1 -1
  4. package/dist/actions/action_rpc.js +4 -6
  5. package/dist/actions/peer_ping.d.ts +8 -0
  6. package/dist/actions/peer_ping.d.ts.map +1 -1
  7. package/dist/actions/peer_ping.js +8 -0
  8. package/dist/actions/peer_request.d.ts +1 -1
  9. package/dist/actions/peer_request.js +1 -1
  10. package/dist/actions/perform_action.d.ts +16 -9
  11. package/dist/actions/perform_action.d.ts.map +1 -1
  12. package/dist/actions/perform_action.js +30 -21
  13. package/dist/actions/register_action_ws.d.ts +1 -1
  14. package/dist/actions/register_action_ws.d.ts.map +1 -1
  15. package/dist/actions/register_action_ws.js +88 -21
  16. package/dist/auth/CLAUDE.md +59 -10
  17. package/dist/auth/account_action_specs.d.ts +0 -2
  18. package/dist/auth/account_action_specs.d.ts.map +1 -1
  19. package/dist/auth/account_actions.d.ts.map +1 -1
  20. package/dist/auth/account_actions.js +10 -1
  21. package/dist/auth/account_queries.d.ts +22 -0
  22. package/dist/auth/account_queries.d.ts.map +1 -1
  23. package/dist/auth/account_queries.js +70 -16
  24. package/dist/auth/account_schema.d.ts +27 -9
  25. package/dist/auth/account_schema.d.ts.map +1 -1
  26. package/dist/auth/account_schema.js +23 -3
  27. package/dist/auth/actor_search_actions.d.ts +3 -3
  28. package/dist/auth/actor_search_actions.d.ts.map +1 -1
  29. package/dist/auth/actor_search_actions.js +10 -6
  30. package/dist/auth/admin_action_specs.d.ts +0 -2
  31. package/dist/auth/admin_action_specs.d.ts.map +1 -1
  32. package/dist/auth/api_token_queries.d.ts +9 -4
  33. package/dist/auth/api_token_queries.d.ts.map +1 -1
  34. package/dist/auth/api_token_queries.js +36 -8
  35. package/dist/auth/app_settings_queries.d.ts +8 -0
  36. package/dist/auth/app_settings_queries.d.ts.map +1 -1
  37. package/dist/auth/app_settings_queries.js +16 -3
  38. package/dist/auth/audit_log_queries.d.ts +21 -3
  39. package/dist/auth/audit_log_queries.d.ts.map +1 -1
  40. package/dist/auth/audit_log_queries.js +38 -7
  41. package/dist/auth/audit_log_schema.d.ts +0 -1
  42. package/dist/auth/audit_log_schema.d.ts.map +1 -1
  43. package/dist/auth/auth_ddl.d.ts +6 -0
  44. package/dist/auth/auth_ddl.d.ts.map +1 -1
  45. package/dist/auth/auth_ddl.js +9 -0
  46. package/dist/auth/cell_actions.d.ts.map +1 -1
  47. package/dist/auth/cell_actions.js +3 -8
  48. package/dist/auth/cell_audit_actions.d.ts.map +1 -1
  49. package/dist/auth/cell_audit_actions.js +1 -1
  50. package/dist/auth/cell_field_actions.js +1 -1
  51. package/dist/auth/cell_grant_actions.js +1 -1
  52. package/dist/auth/cell_item_actions.js +1 -1
  53. package/dist/auth/invite_queries.d.ts +10 -0
  54. package/dist/auth/invite_queries.d.ts.map +1 -1
  55. package/dist/auth/invite_queries.js +28 -7
  56. package/dist/auth/migrations.d.ts.map +1 -1
  57. package/dist/auth/migrations.js +10 -0
  58. package/dist/auth/role_grant_offer_ddl.d.ts +34 -2
  59. package/dist/auth/role_grant_offer_ddl.d.ts.map +1 -1
  60. package/dist/auth/role_grant_offer_ddl.js +61 -2
  61. package/dist/auth/role_grant_offer_queries.d.ts.map +1 -1
  62. package/dist/auth/role_grant_offer_queries.js +48 -53
  63. package/dist/auth/role_grant_queries.d.ts +18 -0
  64. package/dist/auth/role_grant_queries.d.ts.map +1 -1
  65. package/dist/auth/role_grant_queries.js +58 -18
  66. package/dist/auth/session_queries.d.ts +12 -3
  67. package/dist/auth/session_queries.d.ts.map +1 -1
  68. package/dist/auth/session_queries.js +26 -6
  69. package/dist/db/CLAUDE.md +34 -0
  70. package/dist/db/cell_audit_queries.d.ts.map +1 -1
  71. package/dist/db/cell_audit_queries.js +3 -1
  72. package/dist/db/cell_field_queries.d.ts +12 -3
  73. package/dist/db/cell_field_queries.d.ts.map +1 -1
  74. package/dist/db/cell_field_queries.js +25 -7
  75. package/dist/db/cell_grant_queries.d.ts +10 -2
  76. package/dist/db/cell_grant_queries.d.ts.map +1 -1
  77. package/dist/db/cell_grant_queries.js +29 -11
  78. package/dist/db/cell_item_queries.d.ts +13 -2
  79. package/dist/db/cell_item_queries.d.ts.map +1 -1
  80. package/dist/db/cell_item_queries.js +27 -7
  81. package/dist/db/cell_queries.d.ts +32 -3
  82. package/dist/db/cell_queries.d.ts.map +1 -1
  83. package/dist/db/cell_queries.js +57 -22
  84. package/dist/db/fact_queries.d.ts +7 -0
  85. package/dist/db/fact_queries.d.ts.map +1 -1
  86. package/dist/db/fact_queries.js +27 -4
  87. package/dist/db/schema_ready.d.ts +6 -1
  88. package/dist/db/schema_ready.d.ts.map +1 -1
  89. package/dist/db/schema_ready.js +6 -2
  90. package/dist/db/sql_columns.d.ts +144 -0
  91. package/dist/db/sql_columns.d.ts.map +1 -0
  92. package/dist/db/sql_columns.js +164 -0
  93. package/dist/db/sql_identifier.d.ts +6 -5
  94. package/dist/db/sql_identifier.d.ts.map +1 -1
  95. package/dist/db/sql_identifier.js +6 -5
  96. package/dist/http/CLAUDE.md +4 -2
  97. package/dist/http/jsonrpc_helpers.d.ts +13 -0
  98. package/dist/http/jsonrpc_helpers.d.ts.map +1 -1
  99. package/dist/http/jsonrpc_helpers.js +13 -0
  100. package/dist/http/route_spec.d.ts +2 -2
  101. package/dist/server/app_server.d.ts.map +1 -1
  102. package/dist/server/app_server.js +1 -2
  103. package/dist/server/serve_fact_route.d.ts.map +1 -1
  104. package/dist/server/serve_fact_route.js +7 -2
  105. package/dist/testing/CLAUDE.md +55 -28
  106. package/dist/testing/admin_integration.d.ts +0 -3
  107. package/dist/testing/admin_integration.d.ts.map +1 -1
  108. package/dist/testing/admin_integration.js +0 -1
  109. package/dist/testing/adversarial_404.d.ts.map +1 -1
  110. package/dist/testing/adversarial_404.js +4 -1
  111. package/dist/testing/adversarial_input.d.ts.map +1 -1
  112. package/dist/testing/adversarial_input.js +5 -4
  113. package/dist/testing/app_server.d.ts +4 -3
  114. package/dist/testing/app_server.d.ts.map +1 -1
  115. package/dist/testing/app_server.js +11 -5
  116. package/dist/testing/attack_surface.d.ts +16 -0
  117. package/dist/testing/attack_surface.d.ts.map +1 -1
  118. package/dist/testing/attack_surface.js +36 -13
  119. package/dist/testing/audit_completeness.d.ts +0 -3
  120. package/dist/testing/audit_completeness.d.ts.map +1 -1
  121. package/dist/testing/audit_completeness.js +0 -1
  122. package/dist/testing/auth_apps.d.ts +9 -2
  123. package/dist/testing/auth_apps.d.ts.map +1 -1
  124. package/dist/testing/auth_apps.js +9 -2
  125. package/dist/testing/cross_backend/account_lifecycle.d.ts +5 -4
  126. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
  127. package/dist/testing/cross_backend/account_lifecycle.js +7 -0
  128. package/dist/testing/cross_backend/action_manifest.d.ts +19 -3
  129. package/dist/testing/cross_backend/action_manifest.d.ts.map +1 -1
  130. package/dist/testing/cross_backend/action_manifest.js +10 -4
  131. package/dist/testing/cross_backend/action_manifest_parity.d.ts +9 -3
  132. package/dist/testing/cross_backend/action_manifest_parity.d.ts.map +1 -1
  133. package/dist/testing/cross_backend/action_manifest_parity.js +6 -0
  134. package/dist/testing/cross_backend/actor_lookup.d.ts +3 -2
  135. package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
  136. package/dist/testing/cross_backend/body_size.d.ts +4 -2
  137. package/dist/testing/cross_backend/body_size.d.ts.map +1 -1
  138. package/dist/testing/cross_backend/body_size.js +1 -1
  139. package/dist/testing/cross_backend/cell_crud.d.ts +2 -2
  140. package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
  141. package/dist/testing/cross_backend/cell_crud.js +10 -0
  142. package/dist/testing/cross_backend/cell_gated_create.d.ts +3 -3
  143. package/dist/testing/cross_backend/cell_gated_create.d.ts.map +1 -1
  144. package/dist/testing/cross_backend/cell_grant_role.d.ts +2 -2
  145. package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -1
  146. package/dist/testing/cross_backend/cell_relations.d.ts +2 -2
  147. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  148. package/dist/testing/cross_backend/expected_schema.json +1 -1
  149. package/dist/testing/cross_backend/fact_serving.d.ts +2 -2
  150. package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
  151. package/dist/testing/cross_backend/make_cross_backend_project.d.ts +17 -2
  152. package/dist/testing/cross_backend/make_cross_backend_project.d.ts.map +1 -1
  153. package/dist/testing/cross_backend/make_cross_backend_project.js +23 -3
  154. package/dist/testing/cross_backend/origin.d.ts +4 -2
  155. package/dist/testing/cross_backend/origin.d.ts.map +1 -1
  156. package/dist/testing/cross_backend/origin.js +1 -1
  157. package/dist/testing/cross_backend/peer_ping_ws.d.ts.map +1 -1
  158. package/dist/testing/cross_backend/peer_ping_ws.js +11 -7
  159. package/dist/testing/cross_backend/query_shape.d.ts +6 -5
  160. package/dist/testing/cross_backend/query_shape.d.ts.map +1 -1
  161. package/dist/testing/cross_backend/ready.d.ts +2 -2
  162. package/dist/testing/cross_backend/ready.d.ts.map +1 -1
  163. package/dist/testing/cross_backend/role_grant_offer_enumeration.d.ts.map +1 -1
  164. package/dist/testing/cross_backend/role_grant_offer_enumeration.js +7 -1
  165. package/dist/testing/cross_backend/setup.d.ts +26 -6
  166. package/dist/testing/cross_backend/setup.d.ts.map +1 -1
  167. package/dist/testing/cross_backend/standard.d.ts +5 -1
  168. package/dist/testing/cross_backend/standard.d.ts.map +1 -1
  169. package/dist/testing/cross_backend/standard.js +1 -5
  170. package/dist/testing/cross_backend/testing_backdoor.js +1 -1
  171. package/dist/testing/cross_backend/testing_reset_actions.d.ts +5 -0
  172. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  173. package/dist/testing/cross_backend/token_lifetime.d.ts.map +1 -1
  174. package/dist/testing/cross_backend/token_lifetime.js +6 -0
  175. package/dist/testing/cross_backend/token_scope_surface.d.ts +2 -2
  176. package/dist/testing/cross_backend/token_scope_surface.d.ts.map +1 -1
  177. package/dist/testing/cross_backend/wire_shapes.d.ts +22 -0
  178. package/dist/testing/cross_backend/wire_shapes.d.ts.map +1 -0
  179. package/dist/testing/cross_backend/wire_shapes.js +41 -0
  180. package/dist/testing/data_exposure.d.ts +0 -3
  181. package/dist/testing/data_exposure.d.ts.map +1 -1
  182. package/dist/testing/data_exposure.js +0 -1
  183. package/dist/testing/db.d.ts +62 -7
  184. package/dist/testing/db.d.ts.map +1 -1
  185. package/dist/testing/db.js +86 -28
  186. package/dist/testing/integration.d.ts.map +1 -1
  187. package/dist/testing/integration.js +15 -2
  188. package/dist/testing/round_trip.d.ts +0 -3
  189. package/dist/testing/round_trip.d.ts.map +1 -1
  190. package/dist/testing/round_trip.js +0 -4
  191. package/dist/testing/rpc_attack_surface.js +2 -2
  192. package/dist/testing/rpc_round_trip.d.ts +0 -3
  193. package/dist/testing/rpc_round_trip.d.ts.map +1 -1
  194. package/dist/testing/rpc_round_trip.js +0 -1
  195. package/dist/testing/schema_generators.d.ts +15 -0
  196. package/dist/testing/schema_generators.d.ts.map +1 -1
  197. package/dist/testing/schema_generators.js +45 -11
  198. package/dist/testing/standard.d.ts +5 -1
  199. package/dist/testing/standard.d.ts.map +1 -1
  200. package/dist/testing/standard.js +2 -7
  201. package/dist/timestamp.d.ts +58 -0
  202. package/dist/timestamp.d.ts.map +1 -0
  203. package/dist/timestamp.js +57 -0
  204. package/dist/ui/AdminOverview.svelte +5 -10
  205. package/dist/ui/AdminOverview.svelte.d.ts.map +1 -1
  206. package/dist/ui/RoleGrantOfferHistory.svelte +3 -1
  207. package/dist/ui/RoleGrantOfferHistory.svelte.d.ts.map +1 -1
  208. package/dist/ui/role_grant_offers_state.svelte.d.ts.map +1 -1
  209. package/dist/ui/role_grant_offers_state.svelte.js +6 -2
  210. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"attack_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/attack_surface.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAoB7B,OAAO,EAQN,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,MAAM,yBAAyB,CAAC;AAoBjC,OAAO,EAA6B,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAsCpF,oFAAoF;AACpF,MAAM,WAAW,sBAAsB;IACtC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,sBAAsB,KAAG,IA8H3E,CAAC;AAIF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,uCAAuC,GACnD,UAAU,2BAA2B,GAAG,IAAI,GAAG,SAAS,KACtD,2BAA2B,GAAG,IAWhC,CAAC;AAEF,0DAA0D;AAC1D,MAAM,WAAW,4BAA4B;IAC5C,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,sBAAsB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtC,gHAAgH;IAChH,uBAAuB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACvC,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,eAAe,CAAC,EAAE,4BAA4B,CAAC;IAC/C;;;;;;;;;;;OAWG;IACH,sBAAsB,CAAC,EAAE,2BAA2B,GAAG,IAAI,CAAC;CAC5D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,sCAAsC,GAClD,SAAS,4BAA4B,KACnC,IA2EF,CAAC"}
1
+ {"version":3,"file":"attack_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/attack_surface.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAoB7B,OAAO,EAQN,KAAK,4BAA4B,EACjC,KAAK,2BAA2B,EAChC,MAAM,yBAAyB,CAAC;AAqBjC,OAAO,EAA6B,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAoDpF,oFAAoF;AACpF,MAAM,WAAW,sBAAsB;IACtC,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,GAAI,SAAS,sBAAsB,KAAG,IAsI3E,CAAC;AAIF;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,uCAAuC,GACnD,UAAU,2BAA2B,GAAG,IAAI,GAAG,SAAS,KACtD,2BAA2B,GAAG,IAWhC,CAAC;AAEF,0DAA0D;AAC1D,MAAM,WAAW,4BAA4B;IAC5C,+EAA+E;IAC/E,KAAK,EAAE,MAAM,cAAc,CAAC;IAC5B,yDAAyD;IACzD,aAAa,EAAE,MAAM,CAAC;IACtB,iFAAiF;IACjF,sBAAsB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACtC,gHAAgH;IAChH,uBAAuB,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACvC,yDAAyD;IACzD,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACrB,qEAAqE;IACrE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,iEAAiE;IACjE,eAAe,CAAC,EAAE,4BAA4B,CAAC;IAC/C;;;;;;;;;;;OAWG;IACH,sBAAsB,CAAC,EAAE,2BAA2B,GAAG,IAAI,CAAC;IAC5D;;;;;;OAMG;IACH,WAAW,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;CAC5B;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,sCAAsC,GAClD,SAAS,4BAA4B,KACnC,IA4EF,CAAC"}
@@ -18,7 +18,8 @@ import { test, assert, describe } from 'vitest';
18
18
  import { assert_surface_invariants, assert_rpc_ws_surface_invariants, assert_surface_security_policy, audit_error_schema_tightness, assert_error_schema_tightness, default_error_schema_tightness, fuz_app_stock_route_tightness_allowlist } from './surface_invariants.js';
19
19
  import { describe_adversarial_input } from './adversarial_input.js';
20
20
  import { describe_adversarial_404 } from './adversarial_404.js';
21
- import { create_test_app_from_specs, create_test_request_context, create_auth_test_apps, select_auth_app, resolve_test_path } from './auth_apps.js';
21
+ import { create_test_app_from_specs, create_test_request_context, create_auth_test_apps, create_route_skip_filter, select_auth_app } from './auth_apps.js';
22
+ import { resolve_valid_path } from './schema_generators.js';
22
23
  import { assert_surface_matches_snapshot, assert_surface_deterministic, assert_only_expected_public_routes, assert_full_middleware_stack, assert_error_schema_valid } from './assertions.js';
23
24
  import { merge_error_schemas } from '../http/schema_helpers.js';
24
25
  import { collect_middleware_errors } from '../http/surface.js';
@@ -45,6 +46,19 @@ const build_error_schema_lookup = (route_specs, middleware_specs) => {
45
46
  }
46
47
  return lookup;
47
48
  };
49
+ /**
50
+ * Build a lookup from `"METHOD /path"` to the route's own `RouteSpec` — the
51
+ * companion of `build_error_schema_lookup`, so a generated request can reach
52
+ * the route's `params` schema and synthesize path segments the route will
53
+ * actually accept.
54
+ */
55
+ const build_route_spec_lookup = (route_specs) => {
56
+ const lookup = new Map();
57
+ for (const spec of route_specs) {
58
+ lookup.set(`${spec.method} ${spec.path}`, spec);
59
+ }
60
+ return lookup;
61
+ };
48
62
  /**
49
63
  * Generate adversarial HTTP auth enforcement test suites.
50
64
  *
@@ -57,19 +71,26 @@ const build_error_schema_lookup = (route_specs, middleware_specs) => {
57
71
  export const describe_adversarial_auth = (options) => {
58
72
  const { build, roles } = options;
59
73
  const { surface, route_specs, middleware_specs } = build();
60
- const protected_routes = filter_protected_routes(surface);
74
+ const is_skipped = create_route_skip_filter(options.skip_routes);
75
+ const protected_routes = filter_protected_routes(surface).filter((r) => !is_skipped(r));
61
76
  if (protected_routes.length === 0)
62
77
  return;
63
- const role_routes = filter_role_routes(surface);
64
- const keeper_routes = filter_keeper_routes(surface);
78
+ const role_routes = filter_role_routes(surface).filter((r) => !is_skipped(r));
79
+ const keeper_routes = filter_keeper_routes(surface).filter((r) => !is_skipped(r));
65
80
  // merged error schemas (auto-derived + middleware + handler-specific) for response validation
66
81
  const error_schema_lookup = build_error_schema_lookup(route_specs, middleware_specs);
82
+ // The route's own spec, so a path param with a declared format (a
83
+ // `blake3:…` fact hash, a `tok_…` id) is synthesized into a value params
84
+ // validation accepts. Otherwise the generated request 400s on the path
85
+ // before ever reaching the 401 / 403 gate the case is asserting.
86
+ const spec_lookup = build_route_spec_lookup(route_specs);
87
+ const test_path = (route) => resolve_valid_path(route.path, spec_lookup.get(`${route.method} ${route.path}`)?.params);
67
88
  const apps = create_auth_test_apps(route_specs, roles);
68
89
  describe('adversarial HTTP auth enforcement', () => {
69
90
  describe('unauthenticated → 401', () => {
70
91
  for (const route of protected_routes) {
71
92
  test(`${route.method} ${route.path}`, async () => {
72
- const res = await apps.public.request(resolve_test_path(route.path), {
93
+ const res = await apps.public.request(test_path(route), {
73
94
  method: route.method
74
95
  });
75
96
  assert.strictEqual(res.status, 401, `${route.method} ${route.path}`);
@@ -93,7 +114,7 @@ export const describe_adversarial_auth = (options) => {
93
114
  const app = apps.by_role.get(wrong_role);
94
115
  if (!app)
95
116
  throw new Error(`No test app for role '${wrong_role}'`);
96
- const res = await app.request(resolve_test_path(route.path), {
117
+ const res = await app.request(test_path(route), {
97
118
  method: route.method
98
119
  });
99
120
  assert.strictEqual(res.status, 403, `${route.method} ${route.path}`);
@@ -108,7 +129,7 @@ export const describe_adversarial_auth = (options) => {
108
129
  describe('authenticated without role → 403', () => {
109
130
  for (const route of role_only_routes) {
110
131
  test(`${route.method} ${route.path}`, async () => {
111
- const res = await apps.authed.request(resolve_test_path(route.path), {
132
+ const res = await apps.authed.request(test_path(route), {
112
133
  method: route.method
113
134
  });
114
135
  assert.strictEqual(res.status, 403, `${route.method} ${route.path}`);
@@ -126,7 +147,7 @@ export const describe_adversarial_auth = (options) => {
126
147
  const app_session_keeper = create_test_app_from_specs(route_specs, create_test_request_context('keeper'), 'session');
127
148
  for (const route of keeper_routes) {
128
149
  test(`${route.method} ${route.path}`, async () => {
129
- const res = await app_session_keeper.request(resolve_test_path(route.path), {
150
+ const res = await app_session_keeper.request(test_path(route), {
130
151
  method: route.method
131
152
  });
132
153
  assert.strictEqual(res.status, 403, `${route.method} ${route.path}`);
@@ -141,7 +162,9 @@ export const describe_adversarial_auth = (options) => {
141
162
  describe('correct auth passes guard', () => {
142
163
  for (const route of protected_routes) {
143
164
  test(`${route.method} ${route.path}`, async () => {
144
- const res = await select_auth_app(apps, route.auth).request(resolve_test_path(route.path), { method: route.method });
165
+ const res = await select_auth_app(apps, route.auth).request(test_path(route), {
166
+ method: route.method
167
+ });
145
168
  // handler may error (500) or return 404 (stub deps) — that's fine, we only verify auth passed
146
169
  assert.notStrictEqual(res.status, 401, 'should not be 401 (auth rejected)');
147
170
  assert.notStrictEqual(res.status, 403, 'should not be 403 (role rejected)');
@@ -210,7 +233,7 @@ export const resolve_standard_error_schema_tightness = (consumer) => {
210
233
  * any project-specific assertions in additional `describe` blocks.
211
234
  */
212
235
  export const describe_standard_attack_surface_tests = (options) => {
213
- const { build, snapshot_path, expected_public_routes, expected_api_middleware, roles, api_path_prefix = '/api/', security_policy } = options;
236
+ const { build, snapshot_path, expected_public_routes, expected_api_middleware, roles, api_path_prefix = '/api/', security_policy, skip_routes } = options;
214
237
  const error_schema_tightness = resolve_standard_error_schema_tightness(options.error_schema_tightness);
215
238
  const built = build();
216
239
  const { surface } = built;
@@ -254,7 +277,7 @@ export const describe_standard_attack_surface_tests = (options) => {
254
277
  }
255
278
  });
256
279
  });
257
- describe_adversarial_auth({ build: () => built, roles });
258
- describe_adversarial_input({ build: () => built, roles });
259
- describe_adversarial_404({ build: () => built, roles });
280
+ describe_adversarial_auth({ build: () => built, roles, skip_routes });
281
+ describe_adversarial_input({ build: () => built, roles, skip_routes });
282
+ describe_adversarial_404({ build: () => built, roles, skip_routes });
260
283
  };
@@ -2,7 +2,6 @@ import './assert_dev_env.js';
2
2
  import type { SessionOptions } from '../auth/session_cookie.js';
3
3
  import { type RpcEndpointsSuiteOption } from './rpc_helpers.js';
4
4
  import type { AppSurfaceSpec } from '../http/surface.js';
5
- import type { BackendCapabilities } from './cross_backend/capabilities.js';
6
5
  import type { SetupTest } from './cross_backend/setup.js';
7
6
  /**
8
7
  * Configuration for `describe_audit_completeness_tests`.
@@ -23,8 +22,6 @@ export interface AuditCompletenessTestOptions {
23
22
  * options uniformity.
24
23
  */
25
24
  surface_source: AppSurfaceSpec;
26
- /** Backend capability declarations. */
27
- capabilities: BackendCapabilities;
28
25
  /** Session config — needed for factory-form rpc_endpoints resolution. */
29
26
  session_options: SessionOptions<string>;
30
27
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"audit_completeness.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_completeness.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA4B7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAUhE,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AA2B1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AAC3E,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,0BAA0B,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC5C;;;;;OAKG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;OAMG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,uCAAuC;IACvC,YAAY,EAAE,mBAAmB,CAAC;IAClC,yEAAyE;IACzE,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;OAGG;IACH,aAAa,EAAE,uBAAuB,CAAC;CACvC;AAgGD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iCAAiC,GAAI,SAAS,4BAA4B,KAAG,IAinBzF,CAAC"}
1
+ {"version":3,"file":"audit_completeness.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/audit_completeness.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AA4B7B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAUhE,OAAO,EAKN,KAAK,uBAAuB,EAC5B,MAAM,kBAAkB,CAAC;AA2B1B,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,KAAK,EAAE,SAAS,EAAe,MAAM,0BAA0B,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,4BAA4B;IAC5C;;;;;OAKG;IACH,UAAU,EAAE,SAAS,CAAC;IACtB;;;;;;OAMG;IACH,cAAc,EAAE,cAAc,CAAC;IAC/B,yEAAyE;IACzE,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC;;;OAGG;IACH,aAAa,EAAE,uBAAuB,CAAC;CACvC;AAgGD;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,iCAAiC,GAAI,SAAS,4BAA4B,KAAG,IAgnBzF,CAAC"}
@@ -111,7 +111,6 @@ export const describe_audit_completeness_tests = (options) => {
111
111
  // confusing test failure when `rpc_endpoints` is missing.
112
112
  const rpc_endpoints_for_setup = resolve_rpc_endpoints_for_setup(options.rpc_endpoints, options.session_options);
113
113
  const rpc_path = require_rpc_endpoint_path(rpc_endpoints_for_setup);
114
- void options.capabilities;
115
114
  describe('audit_log_completeness', () => {
116
115
  // --- Account routes ---
117
116
  describe('account mutation audit events', () => {
@@ -46,6 +46,13 @@ export declare const create_auth_test_apps: (route_specs: Array<RouteSpec>, role
46
46
  * `create_auth_test_apps`.
47
47
  */
48
48
  export declare const select_auth_app: (apps: AuthTestApps, auth: RouteAuth) => Hono;
49
- /** Replace Hono route params (`:foo`) with dummy values for HTTP testing. */
50
- export declare const resolve_test_path: (path: string) => string;
49
+ /**
50
+ * Build the skip predicate an adversarial suite applies from its
51
+ * `skip_routes` option — routes named in `'METHOD /path'` form, the surface
52
+ * key. `undefined` skips nothing.
53
+ */
54
+ export declare const create_route_skip_filter: (skip_routes: Array<string> | undefined) => ((route: {
55
+ method: string;
56
+ path: string;
57
+ }) => boolean);
51
58
  //# sourceMappingURL=auth_apps.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"auth_apps.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/auth_apps.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;GAOG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvE,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAIN,KAAK,cAAc,EACnB,MAAM,oBAAoB,CAAC;AAI5B;;GAEG;AACH,eAAO,MAAM,2BAA2B,GAAI,OAAO,MAAM,KAAG,cAI1D,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,GACtC,aAAa,KAAK,CAAC,SAAS,CAAC,EAC7B,WAAW,cAAc,EACzB,kBAAkB,cAAc,KAC9B,IAuBF,CAAC;AAEF,sFAAsF;AACtF,MAAM,WAAW,YAAY;IAC5B,MAAM,EAAE,IAAI,CAAC;IACb,MAAM,EAAE,IAAI,CAAC;IACb,MAAM,EAAE,IAAI,CAAC;IACb,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;CAC3B;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GACjC,aAAa,KAAK,CAAC,SAAS,CAAC,EAC7B,OAAO,KAAK,CAAC,MAAM,CAAC,KAClB,YAeF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,YAAY,EAAE,MAAM,SAAS,KAAG,IAarE,CAAC;AAEF,6EAA6E;AAC7E,eAAO,MAAM,iBAAiB,GAAI,MAAM,MAAM,KAAG,MAA4C,CAAC"}
1
+ {"version":3,"file":"auth_apps.d.ts","sourceRoot":"../src/lib/","sources":["../../src/lib/testing/auth_apps.ts"],"names":[],"mappings":"AAAA,OAAO,qBAAqB,CAAC;AAE7B;;;;;;;GAOG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAG5B,OAAO,EAAqB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAkB,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAEvE,OAAO,EAGN,KAAK,cAAc,EACnB,MAAM,4BAA4B,CAAC;AACpC,OAAO,EAIN,KAAK,cAAc,EACnB,MAAM,oBAAoB,CAAC;AAI5B;;GAEG;AACH,eAAO,MAAM,2BAA2B,GAAI,OAAO,MAAM,KAAG,cAI1D,CAAC;AAEH;;;;;;GAMG;AACH,eAAO,MAAM,0BAA0B,GACtC,aAAa,KAAK,CAAC,SAAS,CAAC,EAC7B,WAAW,cAAc,EACzB,kBAAkB,cAAc,KAC9B,IAuBF,CAAC;AAEF,sFAAsF;AACtF,MAAM,WAAW,YAAY;IAC5B,MAAM,EAAE,IAAI,CAAC;IACb,MAAM,EAAE,IAAI,CAAC;IACb,MAAM,EAAE,IAAI,CAAC;IACb,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;CAC3B;AAED;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GACjC,aAAa,KAAK,CAAC,SAAS,CAAC,EAC7B,OAAO,KAAK,CAAC,MAAM,CAAC,KAClB,YAeF,CAAC;AAEF;;;;;;GAMG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,YAAY,EAAE,MAAM,SAAS,KAAG,IAarE,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,GACpC,aAAa,KAAK,CAAC,MAAM,CAAC,GAAG,SAAS,KACpC,CAAC,CAAC,KAAK,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,KAAK,OAAO,CAGvD,CAAC"}
@@ -90,5 +90,12 @@ export const select_auth_app = (apps, auth) => {
90
90
  }
91
91
  return apps.authed;
92
92
  };
93
- /** Replace Hono route params (`:foo`) with dummy values for HTTP testing. */
94
- export const resolve_test_path = (path) => path.replace(/:(\w+)/g, 'test_$1');
93
+ /**
94
+ * Build the skip predicate an adversarial suite applies from its
95
+ * `skip_routes` option — routes named in `'METHOD /path'` form, the surface
96
+ * key. `undefined` skips nothing.
97
+ */
98
+ export const create_route_skip_filter = (skip_routes) => {
99
+ const skip_set = new Set(skip_routes);
100
+ return (route) => skip_set.has(`${route.method} ${route.path}`);
101
+ };
@@ -1,10 +1,11 @@
1
1
  import '../assert_dev_env.js';
2
- import type { RpcPathCrossSuiteOptions } from './setup.js';
2
+ import type { RpcPathCapabilityGatedCrossSuiteOptions } from './setup.js';
3
3
  /**
4
4
  * Options for the account-lifecycle parity suite. The standard
5
- * RPC-dispatched cross-suite shape (`setup_test` / `capabilities` /
6
- * `rpc_path`); aliases `RpcPathCrossSuiteOptions` rather than duplicating.
5
+ * capability-gating RPC-dispatched cross-suite shape (`setup_test` /
6
+ * `capabilities` / `rpc_path`); aliases
7
+ * `RpcPathCapabilityGatedCrossSuiteOptions` rather than duplicating.
7
8
  */
8
- export type AccountLifecycleCrossTestOptions = RpcPathCrossSuiteOptions;
9
+ export type AccountLifecycleCrossTestOptions = RpcPathCapabilityGatedCrossSuiteOptions;
9
10
  export declare const describe_account_lifecycle_cross_tests: (options: AccountLifecycleCrossTestOptions) => void;
10
11
  //# sourceMappingURL=account_lifecycle.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"account_lifecycle.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/account_lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAqC9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG3D;;;;GAIG;AACH,MAAM,MAAM,gCAAgC,GAAG,wBAAwB,CAAC;AAExE,eAAO,MAAM,sCAAsC,GAClD,SAAS,gCAAgC,KACvC,IA+TF,CAAC"}
1
+ {"version":3,"file":"account_lifecycle.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/account_lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAsC9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAG1E;;;;;GAKG;AACH,MAAM,MAAM,gCAAgC,GAAG,uCAAuC,CAAC;AAEvF,eAAO,MAAM,sCAAsC,GAClD,SAAS,gCAAgC,KACvC,IAqUF,CAAC"}
@@ -22,6 +22,7 @@ import { AccountDeleteOutput, AccountUndeleteOutput, AccountPurgeOutput, AdminAc
22
22
  import { ERROR_ACCOUNT_NOT_FOUND, ERROR_AUTHENTICATION_REQUIRED } from '../../http/error_schemas.js';
23
23
  import { test_if } from './capabilities.js';
24
24
  import { cross_rpc_call, error_reason, expect_output } from './cell_cross_helpers.js';
25
+ import { assert_iso8601_seconds } from './wire_shapes.js';
25
26
  import { SPINE_RPC_PATH } from './spine_surface_constants.js';
26
27
  export const describe_account_lifecycle_cross_tests = (options) => {
27
28
  const { setup_test, capabilities } = options;
@@ -99,6 +100,9 @@ export const describe_account_lifecycle_cross_tests = (options) => {
99
100
  const failure = listed.events.find((e) => e.outcome === 'failure' &&
100
101
  e.metadata?.reason === ERROR_CANNOT_DELETE_KEEPER);
101
102
  assert.ok(failure, 'keeper-removal guard must emit an account_delete outcome=failure audit row with reason cannot_delete_keeper');
103
+ // Byte-shape gate on the audit row's timestamp — `z.string()` in
104
+ // `AuditLogEventWithUsernamesJson` accepts a millisecond stamp too.
105
+ assert_iso8601_seconds(failure.created_at, 'audit_log_list.created_at');
102
106
  });
103
107
  test_if(capabilities.account_lifecycle, 'deterministic: double-undelete → second call is not_found', async () => {
104
108
  const fixture = await setup_test();
@@ -139,6 +143,9 @@ export const describe_account_lifecycle_cross_tests = (options) => {
139
143
  const row = with_deleted.accounts.find((a) => a.account.id === victim.account.id);
140
144
  assert.ok(row, 'include_deleted surfaces the tombstoned row');
141
145
  assert.ok(row.account.deleted_at !== null, 'tombstoned row carries a non-null deleted_at on both spines');
146
+ assert_iso8601_seconds(row.account.created_at, 'admin_account_list.created_at');
147
+ assert_iso8601_seconds(row.account.updated_at, 'admin_account_list.updated_at');
148
+ assert_iso8601_seconds(row.account.deleted_at, 'admin_account_list.deleted_at');
142
149
  });
143
150
  });
144
151
  };
@@ -2,7 +2,7 @@ import '../assert_dev_env.js';
2
2
  /**
3
3
  * Cross-impl RPC **action-manifest** introspection — a normalized,
4
4
  * JSON-serializable dump of a backend's live RPC method set, one entry per
5
- * method carrying its auth shape + side-effect flag.
5
+ * method carrying its auth shape, side-effect flag, and rate-limit class.
6
6
  *
7
7
  * The sibling of `schema_introspect.ts`'s `SchemaSnapshot`: where that
8
8
  * captures the live *database* shape for the schema-parity gate, this
@@ -35,13 +35,16 @@ import '../assert_dev_env.js';
35
35
  */
36
36
  import { z } from 'zod';
37
37
  import { type RouteAuth } from '../../http/auth_shape.js';
38
+ import { RateLimitKey } from '../../http/error_schemas.js';
38
39
  import type { RequestResponseActionSpec } from '../../actions/action_spec.js';
39
40
  /**
40
41
  * One method's normalized RPC metadata — the cross-impl-comparable unit.
41
42
  * `roles` / `credential_types` are always present + sorted (an absent gate
42
43
  * and an empty list both serialize to `[]`) so the diff never trips on a
43
44
  * `undefined`-vs-`[]` or declaration-order difference between impls; the
44
- * auth axes reuse the canonical `AuthAxisState` enum.
45
+ * auth axes reuse the canonical `AuthAxisState` enum. `rate_limit` is
46
+ * nullable rather than optional for the same reason — an unthrottled action
47
+ * serializes an explicit `null`, so a missing key is drift, not a default.
45
48
  */
46
49
  export declare const ActionManifestEntry: z.ZodObject<{
47
50
  /** RPC method name (`spec.method`). */
@@ -64,6 +67,12 @@ export declare const ActionManifestEntry: z.ZodObject<{
64
67
  roles: z.ZodArray<z.ZodString>;
65
68
  /** Permitted credential channels, sorted; `[]` when ungated (any credential). */
66
69
  credential_types: z.ZodArray<z.ZodString>;
70
+ /** Per-action rate-limit class, or `null` when the action declares none. */
71
+ rate_limit: z.ZodNullable<z.ZodEnum<{
72
+ account: "account";
73
+ ip: "ip";
74
+ both: "both";
75
+ }>>;
67
76
  }, z.core.$strict>;
68
77
  export type ActionManifestEntry = z.infer<typeof ActionManifestEntry>;
69
78
  /** The full action manifest — every entry, sorted by `method`. */
@@ -89,6 +98,12 @@ export declare const ActionManifest: z.ZodObject<{
89
98
  roles: z.ZodArray<z.ZodString>;
90
99
  /** Permitted credential channels, sorted; `[]` when ungated (any credential). */
91
100
  credential_types: z.ZodArray<z.ZodString>;
101
+ /** Per-action rate-limit class, or `null` when the action declares none. */
102
+ rate_limit: z.ZodNullable<z.ZodEnum<{
103
+ account: "account";
104
+ ip: "ip";
105
+ both: "both";
106
+ }>>;
92
107
  }, z.core.$strict>>;
93
108
  }, z.core.$strict>;
94
109
  export type ActionManifest = z.infer<typeof ActionManifest>;
@@ -101,6 +116,7 @@ export declare const action_manifest_entry: (spec: {
101
116
  readonly method: string;
102
117
  readonly auth: RouteAuth;
103
118
  readonly side_effects: boolean;
119
+ readonly rate_limit?: RateLimitKey;
104
120
  }) => ActionManifestEntry;
105
121
  /**
106
122
  * Build the normalized `ActionManifest` from a list of request-response
@@ -109,5 +125,5 @@ export declare const action_manifest_entry: (spec: {
109
125
  * it passes (the TS spine passes its full mount; the Rust stub filters
110
126
  * `PROTOCOL_ACTION_SPECS` first) — see the module doc.
111
127
  */
112
- export declare const build_action_manifest: (specs: ReadonlyArray<Pick<RequestResponseActionSpec, "method" | "auth" | "side_effects">>) => ActionManifest;
128
+ export declare const build_action_manifest: (specs: ReadonlyArray<Pick<RequestResponseActionSpec, "method" | "auth" | "side_effects" | "rate_limit">>) => ActionManifest;
113
129
  //# sourceMappingURL=action_manifest.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"action_manifest.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/action_manifest.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACzE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AAE9E;;;;;;GAMG;AACH,eAAO,MAAM,mBAAmB;IAC/B,uCAAuC;;IAEvC,iGAAiG;;IAEjG,+BAA+B;;;;;;IAE/B,6BAA6B;;;;;;IAE7B,mFAAmF;;IAEnF,iFAAiF;;kBAEhF,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,kEAAkE;AAClE,eAAO,MAAM,cAAc;;QAhB1B,uCAAuC;;QAEvC,iGAAiG;;QAEjG,+BAA+B;;;;;;QAE/B,6BAA6B;;;;;;QAE7B,mFAAmF;;QAEnF,iFAAiF;;;kBAQhF,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAM5D;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAAI,MAAM;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;CAC/B,KAAG,mBAOF,CAAC;AAMH;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GACjC,OAAO,aAAa,CAAC,IAAI,CAAC,yBAAyB,EAAE,QAAQ,GAAG,MAAM,GAAG,cAAc,CAAC,CAAC,KACvF,cAED,CAAC"}
1
+ {"version":3,"file":"action_manifest.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/action_manifest.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAiB,KAAK,SAAS,EAAE,MAAM,0BAA0B,CAAC;AACzE,OAAO,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAC3D,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,8BAA8B,CAAC;AAE9E;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB;IAC/B,uCAAuC;;IAEvC,iGAAiG;;IAEjG,+BAA+B;;;;;;IAE/B,6BAA6B;;;;;;IAE7B,mFAAmF;;IAEnF,iFAAiF;;IAEjF,4EAA4E;;;;;;kBAE3E,CAAC;AACH,MAAM,MAAM,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEtE,kEAAkE;AAClE,eAAO,MAAM,cAAc;;QAlB1B,uCAAuC;;QAEvC,iGAAiG;;QAEjG,+BAA+B;;;;;;QAE/B,6BAA6B;;;;;;QAE7B,mFAAmF;;QAEnF,iFAAiF;;QAEjF,4EAA4E;;;;;;;kBAQ3E,CAAC;AACH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,cAAc,CAAC,CAAC;AAM5D;;;;GAIG;AACH,eAAO,MAAM,qBAAqB,GAAI,MAAM;IAC3C,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;IACzB,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;IAC/B,QAAQ,CAAC,UAAU,CAAC,EAAE,YAAY,CAAC;CACnC,KAAG,mBAQF,CAAC;AAMH;;;;;;GAMG;AACH,eAAO,MAAM,qBAAqB,GACjC,OAAO,aAAa,CACnB,IAAI,CAAC,yBAAyB,EAAE,QAAQ,GAAG,MAAM,GAAG,cAAc,GAAG,YAAY,CAAC,CAClF,KACC,cAED,CAAC"}
@@ -2,7 +2,7 @@ import '../assert_dev_env.js';
2
2
  /**
3
3
  * Cross-impl RPC **action-manifest** introspection — a normalized,
4
4
  * JSON-serializable dump of a backend's live RPC method set, one entry per
5
- * method carrying its auth shape + side-effect flag.
5
+ * method carrying its auth shape, side-effect flag, and rate-limit class.
6
6
  *
7
7
  * The sibling of `schema_introspect.ts`'s `SchemaSnapshot`: where that
8
8
  * captures the live *database* shape for the schema-parity gate, this
@@ -35,12 +35,15 @@ import '../assert_dev_env.js';
35
35
  */
36
36
  import { z } from 'zod';
37
37
  import { AuthAxisState } from '../../http/auth_shape.js';
38
+ import { RateLimitKey } from '../../http/error_schemas.js';
38
39
  /**
39
40
  * One method's normalized RPC metadata — the cross-impl-comparable unit.
40
41
  * `roles` / `credential_types` are always present + sorted (an absent gate
41
42
  * and an empty list both serialize to `[]`) so the diff never trips on a
42
43
  * `undefined`-vs-`[]` or declaration-order difference between impls; the
43
- * auth axes reuse the canonical `AuthAxisState` enum.
44
+ * auth axes reuse the canonical `AuthAxisState` enum. `rate_limit` is
45
+ * nullable rather than optional for the same reason — an unthrottled action
46
+ * serializes an explicit `null`, so a missing key is drift, not a default.
44
47
  */
45
48
  export const ActionManifestEntry = z.strictObject({
46
49
  /** RPC method name (`spec.method`). */
@@ -54,7 +57,9 @@ export const ActionManifestEntry = z.strictObject({
54
57
  /** Required roles (any-of), sorted; `[]` when the action declares no role gate. */
55
58
  roles: z.array(z.string()),
56
59
  /** Permitted credential channels, sorted; `[]` when ungated (any credential). */
57
- credential_types: z.array(z.string())
60
+ credential_types: z.array(z.string()),
61
+ /** Per-action rate-limit class, or `null` when the action declares none. */
62
+ rate_limit: RateLimitKey.nullable()
58
63
  });
59
64
  /** The full action manifest — every entry, sorted by `method`. */
60
65
  export const ActionManifest = z.strictObject({
@@ -73,7 +78,8 @@ export const action_manifest_entry = (spec) => ({
73
78
  account: spec.auth.account,
74
79
  actor: spec.auth.actor,
75
80
  roles: sorted_strings(spec.auth.roles),
76
- credential_types: sorted_strings(spec.auth.credential_types)
81
+ credential_types: sorted_strings(spec.auth.credential_types),
82
+ rate_limit: spec.rate_limit ?? null
77
83
  });
78
84
  /** Deterministic byte-lexicographic method order — matches the Rust `str::cmp` the stub sorts by. */
79
85
  const compare_method = (a, b) => a.method < b.method ? -1 : a.method > b.method ? 1 : 0;
@@ -7,8 +7,8 @@ import '../assert_dev_env.js';
7
7
  * fuz_app spine and the Rust `testing_spine_stub`) are each other's parity
8
8
  * reference. After both bootstrap, dump each one's live RPC method set, diff,
9
9
  * fail loudly on drift. The diff entries name the specific divergence (a
10
- * method only one impl mounts, a per-method auth-axis or side-effect
11
- * mismatch) so the error message points at the source.
10
+ * method only one impl mounts, a per-method auth-axis, side-effect, or
11
+ * rate-limit-class mismatch) so the error message points at the source.
12
12
  *
13
13
  * ```ts
14
14
  * const manifest_a = await capture_action_manifest(ts_handle);
@@ -17,13 +17,14 @@ import '../assert_dev_env.js';
17
17
  * ```
18
18
  *
19
19
  * Non-coverage: the manifest captures `{method, side_effects, account, actor,
20
- * roles, credential_types}` — the wire-relevant auth shape. It does **not**
20
+ * roles, credential_types, rate_limit}` — the wire-relevant auth shape. It does **not**
21
21
  * capture input/output schemas (the declared-surface wire shape is gated by
22
22
  * `rpc_round_trip`) nor the protocol actions `heartbeat` / `cancel` (excluded
23
23
  * by construction — see `action_manifest.ts`).
24
24
  *
25
25
  * @module
26
26
  */
27
+ import type { RateLimitKey } from '../../http/error_schemas.js';
27
28
  import type { ActionManifest } from './action_manifest.js';
28
29
  /** Structured drift entry. `where` is the named source impl ('a' or 'b'). */
29
30
  export type ActionManifestDiff = {
@@ -35,6 +36,11 @@ export type ActionManifestDiff = {
35
36
  readonly method: string;
36
37
  readonly a: boolean;
37
38
  readonly b: boolean;
39
+ } | {
40
+ readonly kind: 'rate_limit_differs';
41
+ readonly method: string;
42
+ readonly a: RateLimitKey | null;
43
+ readonly b: RateLimitKey | null;
38
44
  } | {
39
45
  readonly kind: 'auth_field_differs';
40
46
  readonly method: string;
@@ -1 +1 @@
1
- {"version":3,"file":"action_manifest_parity.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/action_manifest_parity.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAuB,MAAM,sBAAsB,CAAC;AAWhF,6EAA6E;AAC7E,MAAM,MAAM,kBAAkB,GAC3B;IAAE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,GAAG,GAAG,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvF;IACA,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC;CACnB,GACD;IACA,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,kBAAkB,CAAC;IACnE,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAML;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GACjC,GAAG,cAAc,EACjB,GAAG,cAAc,KACf,KAAK,CAAC,kBAAkB,CA+B1B,CAAC;AAEF,qEAAqE;AACrE,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,eAAO,MAAM,4BAA4B,GACxC,OAAO,aAAa,CAAC,kBAAkB,CAAC,EACxC,SAAQ,wBAA6B,KACnC,MA8BF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,6BAA6B,GACzC,GAAG,cAAc,EACjB,GAAG,cAAc,EACjB,SAAQ,wBAA6B,KACnC,IAUF,CAAC"}
1
+ {"version":3,"file":"action_manifest_parity.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/action_manifest_parity.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,cAAc,EAAuB,MAAM,sBAAsB,CAAC;AAWhF,6EAA6E;AAC7E,MAAM,MAAM,kBAAkB,GAC3B;IAAE,QAAQ,CAAC,IAAI,EAAE,gBAAgB,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,GAAG,GAAG,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,GACvF;IACA,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAC;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC;CACnB,GACD;IACA,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,CAAC,EAAE,YAAY,GAAG,IAAI,CAAC;CAC/B,GACD;IACA,QAAQ,CAAC,IAAI,EAAE,oBAAoB,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,kBAAkB,CAAC;IACnE,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC;IACpB,QAAQ,CAAC,CAAC,EAAE,OAAO,CAAC;CACnB,CAAC;AAML;;;;;GAKG;AACH,eAAO,MAAM,qBAAqB,GACjC,GAAG,cAAc,EACjB,GAAG,cAAc,KACf,KAAK,CAAC,kBAAkB,CAkC1B,CAAC;AAEF,qEAAqE;AACrE,MAAM,WAAW,wBAAwB;IACxC,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,CAAC,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;GAGG;AACH,eAAO,MAAM,4BAA4B,GACxC,OAAO,aAAa,CAAC,kBAAkB,CAAC,EACxC,SAAQ,wBAA6B,KACnC,MAqCF,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,6BAA6B,GACzC,GAAG,cAAc,EACjB,GAAG,cAAc,EACjB,SAAQ,wBAA6B,KACnC,IAUF,CAAC"}
@@ -30,6 +30,9 @@ export const diff_action_manifests = (a, b) => {
30
30
  if (ea.side_effects !== eb.side_effects) {
31
31
  diffs.push({ kind: 'side_effects_differ', method, a: ea.side_effects, b: eb.side_effects });
32
32
  }
33
+ if (ea.rate_limit !== eb.rate_limit) {
34
+ diffs.push({ kind: 'rate_limit_differs', method, a: ea.rate_limit, b: eb.rate_limit });
35
+ }
33
36
  for (const field of AUTH_SCALAR_FIELDS) {
34
37
  if (ea[field] !== eb[field]) {
35
38
  diffs.push({ kind: 'auth_field_differs', method, field, a: ea[field], b: eb[field] });
@@ -62,6 +65,9 @@ export const format_action_manifest_diffs = (diffs, labels = {}) => {
62
65
  case 'side_effects_differ':
63
66
  lines.push(` ${d.method} side_effects differs: ${label_a}=${d.a}, ${label_b}=${d.b}`);
64
67
  break;
68
+ case 'rate_limit_differs':
69
+ lines.push(` ${d.method} rate_limit differs: ${label_a}=${JSON.stringify(d.a)}, ${label_b}=${JSON.stringify(d.b)}`);
70
+ break;
65
71
  case 'auth_field_differs':
66
72
  lines.push(` ${d.method} auth.${d.field} differs: ${label_a}=${JSON.stringify(d.a)}, ${label_b}=${JSON.stringify(d.b)}`);
67
73
  break;
@@ -2,8 +2,9 @@ import '../assert_dev_env.js';
2
2
  import type { RpcPathCrossSuiteOptions } from './setup.js';
3
3
  /**
4
4
  * Options for the actor-lookup parity suite. The standard RPC-dispatched
5
- * cross-suite shape (`setup_test` / `capabilities` / `rpc_path`); aliases
6
- * the shared `RpcPathCrossSuiteOptions` rather than minting a duplicate.
5
+ * cross-suite shape (`setup_test` / `rpc_path`); aliases the shared
6
+ * `RpcPathCrossSuiteOptions` rather than minting a duplicate. No case here
7
+ * is capability-gated, so the flag bundle is not on the shape.
7
8
  */
8
9
  export type ActorLookupCrossTestOptions = RpcPathCrossSuiteOptions;
9
10
  export declare const describe_actor_lookup_cross_tests: (options: ActorLookupCrossTestOptions) => void;
@@ -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;AAqC9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG3D;;;;GAIG;AACH,MAAM,MAAM,2BAA2B,GAAG,wBAAwB,CAAC;AASnE,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IAkDxF,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;AAqC9B,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,IAkDxF,CAAC"}
@@ -2,8 +2,10 @@ import '../assert_dev_env.js';
2
2
  import type { RpcPathCrossSuiteOptions } from './setup.js';
3
3
  /**
4
4
  * Options for the body-size parity suite — the standard RPC-dispatched
5
- * cross-suite shape (`setup_test` / `capabilities` / `rpc_path`); aliases the
6
- * shared `RpcPathCrossSuiteOptions` rather than minting a duplicate.
5
+ * cross-suite shape (`setup_test` / `rpc_path`); aliases the shared
6
+ * `RpcPathCrossSuiteOptions` rather than minting a duplicate. The limit is
7
+ * on every spine, so no case is capability-gated and the flag bundle is not
8
+ * on the shape.
7
9
  */
8
10
  export type BodySizeCrossTestOptions = RpcPathCrossSuiteOptions;
9
11
  export declare const describe_body_size_cross_tests: (options: BodySizeCrossTestOptions) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"body_size.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/body_size.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA6D9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG3D;;;;GAIG;AACH,MAAM,MAAM,wBAAwB,GAAG,wBAAwB,CAAC;AAmDhE,eAAO,MAAM,8BAA8B,GAAI,SAAS,wBAAwB,KAAG,IA0ClF,CAAC"}
1
+ {"version":3,"file":"body_size.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/body_size.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA6D9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG3D;;;;;;GAMG;AACH,MAAM,MAAM,wBAAwB,GAAG,wBAAwB,CAAC;AAmDhE,eAAO,MAAM,8BAA8B,GAAI,SAAS,wBAAwB,KAAG,IA0ClF,CAAC"}
@@ -39,7 +39,7 @@ import '../assert_dev_env.js';
39
39
  * Like origin/payload rejection, this is middleware-level flat REST — not the
40
40
  * JSON-RPC envelope the conformance-table runner expects — so it's an
41
41
  * imperative suite, not a `conformance_table` row. Runs both legs via the
42
- * shared `{setup_test, capabilities}` protocol: the in-process leg
42
+ * shared `setup_test` protocol: the in-process leg
43
43
  * (`auth/body_size_parity.db.test.ts`, plain `gro test`) and the cross-process
44
44
  * leg (`cross_backend/body_size.cross.test.ts`, the TS spine binaries + Rust
45
45
  * `testing_spine_stub` over real HTTP). The body-size limit is on every spine,
@@ -1,4 +1,4 @@
1
1
  import '../assert_dev_env.js';
2
- import type { RpcPathCrossSuiteOptions } from './setup.js';
3
- export declare const describe_cell_crud_cross_tests: (options: RpcPathCrossSuiteOptions) => void;
2
+ import type { RpcPathCapabilityGatedCrossSuiteOptions } from './setup.js';
3
+ export declare const describe_cell_crud_cross_tests: (options: RpcPathCapabilityGatedCrossSuiteOptions) => void;
4
4
  //# sourceMappingURL=cell_crud.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cell_crud.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_crud.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA2D9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG3D,eAAO,MAAM,8BAA8B,GAAI,SAAS,wBAAwB,KAAG,IAsclF,CAAC"}
1
+ {"version":3,"file":"cell_crud.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_crud.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA4D9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAG1E,eAAO,MAAM,8BAA8B,GAC1C,SAAS,uCAAuC,KAC9C,IA+cF,CAAC"}
@@ -48,6 +48,7 @@ import { describe, assert } from 'vitest';
48
48
  import { CellCreateOutput, CellDeleteOutput, CellGetOutput, CellListOutput, CellUpdateOutput } from '../../auth/cell_action_specs.js';
49
49
  import { test_if } from './capabilities.js';
50
50
  import { cross_rpc_call, error_reason, expect_output } from './cell_cross_helpers.js';
51
+ import { assert_iso8601_seconds, assert_iso8601_seconds_nullable } from './wire_shapes.js';
51
52
  import { SPINE_RPC_PATH } from './spine_surface_constants.js';
52
53
  export const describe_cell_crud_cross_tests = (options) => {
53
54
  const { setup_test, capabilities } = options;
@@ -64,6 +65,12 @@ export const describe_cell_crud_cross_tests = (options) => {
64
65
  assert.strictEqual(created.cell.created_by, owner.actor.id);
65
66
  assert.strictEqual(created.cell.updated_by, null);
66
67
  assert.strictEqual(created.cell.grant_count, 0);
68
+ // Byte-shape gate: `CellJson`'s `z.string()` accepts a millisecond
69
+ // stamp too, so `expect_output` alone can't tell the two spines
70
+ // apart on the timestamp columns.
71
+ assert_iso8601_seconds(created.cell.created_at, 'cell_create.created_at');
72
+ assert_iso8601_seconds_nullable(created.cell.updated_at, 'cell_create.updated_at');
73
+ assert_iso8601_seconds_nullable(created.cell.deleted_at, 'cell_create.deleted_at');
67
74
  const cell_id = created.cell.id;
68
75
  // get by id — full CellGetOutput envelope; relations empty in the first cut
69
76
  const got = expect_output(await cross_rpc_call(t, rpc_path, 'cell_get', { id: cell_id }, owner_headers), CellGetOutput);
@@ -74,10 +81,13 @@ export const describe_cell_crud_cross_tests = (options) => {
74
81
  assert.strictEqual(got.items_truncated, false);
75
82
  assert.strictEqual(got.can_edit, true);
76
83
  assert.strictEqual(got.can_grant, true); // owner is manage-tier
84
+ assert_iso8601_seconds(got.cell.created_at, 'cell_get.created_at');
85
+ assert_iso8601_seconds_nullable(got.cell.updated_at, 'cell_get.updated_at');
77
86
  // update data + flip to public (owner is manage-tier, so visibility write is allowed)
78
87
  const updated = expect_output(await cross_rpc_call(t, rpc_path, 'cell_update', { cell_id, data: { label: 'hi2' }, visibility: 'public' }, owner_headers), CellUpdateOutput);
79
88
  assert.strictEqual(updated.cell.visibility, 'public');
80
89
  assert.strictEqual(updated.cell.updated_by, owner.actor.id);
90
+ assert_iso8601_seconds(updated.cell.updated_at, 'cell_update.updated_at');
81
91
  // list includes it
82
92
  const listed = expect_output(await cross_rpc_call(t, rpc_path, 'cell_list', {}, owner_headers), CellListOutput);
83
93
  assert.ok(listed.cells.some((c) => c.id === cell_id), 'owner cell_list omitted the cell');
@@ -1,6 +1,6 @@
1
1
  import '../assert_dev_env.js';
2
- import type { RpcPathCrossSuiteOptions } from './setup.js';
3
- export declare const describe_cell_gated_create_cross_tests: (options: RpcPathCrossSuiteOptions) => void;
2
+ import type { RpcPathCapabilityGatedCrossSuiteOptions } from './setup.js';
3
+ export declare const describe_cell_gated_create_cross_tests: (options: RpcPathCapabilityGatedCrossSuiteOptions) => void;
4
4
  /**
5
5
  * Cross-backend parity for the **`cell_moderate` verb** (the `pending →
6
6
  * approved | rejected` transition) — root-authority-gated.
@@ -24,5 +24,5 @@ export declare const describe_cell_gated_create_cross_tests: (options: RpcPathCr
24
24
  *
25
25
  * `$lib`-free by contract (relative specifiers only).
26
26
  */
27
- export declare const describe_cell_moderate_cross_tests: (options: RpcPathCrossSuiteOptions) => void;
27
+ export declare const describe_cell_moderate_cross_tests: (options: RpcPathCapabilityGatedCrossSuiteOptions) => void;
28
28
  //# sourceMappingURL=cell_gated_create.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cell_gated_create.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_gated_create.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAiD9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAkB3D,eAAO,MAAM,sCAAsC,GAAI,SAAS,wBAAwB,KAAG,IAwP1F,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,kCAAkC,GAAI,SAAS,wBAAwB,KAAG,IAwHtF,CAAC"}
1
+ {"version":3,"file":"cell_gated_create.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_gated_create.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAiD9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAkB1E,eAAO,MAAM,sCAAsC,GAClD,SAAS,uCAAuC,KAC9C,IAwPF,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,eAAO,MAAM,kCAAkC,GAC9C,SAAS,uCAAuC,KAC9C,IAwHF,CAAC"}
@@ -1,8 +1,8 @@
1
1
  import '../assert_dev_env.js';
2
- import type { RpcPathCrossSuiteOptions } from './setup.js';
2
+ import type { RpcPathCapabilityGatedCrossSuiteOptions } from './setup.js';
3
3
  /** App role the holder is seeded with; matches the spine's registered role. */
4
4
  export declare const CELL_EDITOR_ROLE = "cell_editor";
5
5
  /** Username the fixture seeds (via `extra_accounts`) holding `CELL_EDITOR_ROLE`. */
6
6
  export declare const CELL_ROLE_HOLDER_USERNAME = "cell_role_holder";
7
- export declare const describe_cell_grant_role_cross_tests: (options: RpcPathCrossSuiteOptions) => void;
7
+ export declare const describe_cell_grant_role_cross_tests: (options: RpcPathCapabilityGatedCrossSuiteOptions) => void;
8
8
  //# sourceMappingURL=cell_grant_role.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cell_grant_role.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_grant_role.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAmD9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAG3D,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,gBAAyB,CAAC;AAEvD,oFAAoF;AACpF,eAAO,MAAM,yBAAyB,qBAAqB,CAAC;AAK5D,eAAO,MAAM,oCAAoC,GAAI,SAAS,wBAAwB,KAAG,IAsOxF,CAAC"}
1
+ {"version":3,"file":"cell_grant_role.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_grant_role.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAmD9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAG1E,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,gBAAyB,CAAC;AAEvD,oFAAoF;AACpF,eAAO,MAAM,yBAAyB,qBAAqB,CAAC;AAK5D,eAAO,MAAM,oCAAoC,GAChD,SAAS,uCAAuC,KAC9C,IAsOF,CAAC"}
@@ -1,4 +1,4 @@
1
1
  import '../assert_dev_env.js';
2
- import type { RpcPathCrossSuiteOptions } from './setup.js';
3
- export declare const describe_cell_relations_cross_tests: (options: RpcPathCrossSuiteOptions) => void;
2
+ import type { RpcPathCapabilityGatedCrossSuiteOptions } from './setup.js';
3
+ export declare const describe_cell_relations_cross_tests: (options: RpcPathCapabilityGatedCrossSuiteOptions) => void;
4
4
  //# sourceMappingURL=cell_relations.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"cell_relations.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_relations.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA4F9B,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,YAAY,CAAC;AAsF3D,eAAO,MAAM,mCAAmC,GAAI,SAAS,wBAAwB,KAAG,IAi2BvF,CAAC"}
1
+ {"version":3,"file":"cell_relations.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_relations.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA4F9B,OAAO,KAAK,EAAE,uCAAuC,EAAE,MAAM,YAAY,CAAC;AAsF1E,eAAO,MAAM,mCAAmC,GAC/C,SAAS,uCAAuC,KAC9C,IAi2BF,CAAC"}
@@ -47,7 +47,7 @@
47
47
  "target_account_id",
48
48
  "target_actor_id"
49
49
  ],
50
- "auth_session": ["account_id", "created_at", "expires_at", "id", "last_seen_at"],
50
+ "auth_session": ["account_id", "created_at", "expires_at", "id"],
51
51
  "bootstrap_lock": ["bootstrapped", "id"],
52
52
  "cell": [
53
53
  "created_at",