@fuzdev/fuz_app 0.86.0 → 0.88.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 (106) hide show
  1. package/dist/actions/action_rpc.js +1 -1
  2. package/dist/actions/register_action_ws.js +1 -1
  3. package/dist/auth/CLAUDE.md +15 -0
  4. package/dist/auth/account_actions.js +1 -1
  5. package/dist/auth/account_route_schema.d.ts +146 -0
  6. package/dist/auth/account_route_schema.d.ts.map +1 -0
  7. package/dist/auth/account_route_schema.js +141 -0
  8. package/dist/auth/account_routes.d.ts +0 -79
  9. package/dist/auth/account_routes.d.ts.map +1 -1
  10. package/dist/auth/account_routes.js +15 -110
  11. package/dist/auth/audit_log_route_schema.d.ts +32 -0
  12. package/dist/auth/audit_log_route_schema.d.ts.map +1 -0
  13. package/dist/auth/audit_log_route_schema.js +36 -0
  14. package/dist/auth/audit_log_routes.d.ts.map +1 -1
  15. package/dist/auth/audit_log_routes.js +2 -12
  16. package/dist/auth/bearer_auth.js +1 -1
  17. package/dist/auth/bootstrap_route_schema.d.ts +85 -0
  18. package/dist/auth/bootstrap_route_schema.d.ts.map +1 -0
  19. package/dist/auth/bootstrap_route_schema.js +56 -0
  20. package/dist/auth/bootstrap_routes.d.ts +0 -20
  21. package/dist/auth/bootstrap_routes.d.ts.map +1 -1
  22. package/dist/auth/bootstrap_routes.js +4 -35
  23. package/dist/auth/signup_route_schema.d.ts +53 -0
  24. package/dist/auth/signup_route_schema.d.ts.map +1 -0
  25. package/dist/auth/signup_route_schema.js +59 -0
  26. package/dist/auth/signup_routes.d.ts +0 -26
  27. package/dist/auth/signup_routes.d.ts.map +1 -1
  28. package/dist/auth/signup_routes.js +8 -40
  29. package/dist/http/CLAUDE.md +2 -1
  30. package/dist/http/client_ip.d.ts +15 -0
  31. package/dist/http/client_ip.d.ts.map +1 -0
  32. package/dist/http/client_ip.js +13 -0
  33. package/dist/http/proxy.d.ts +0 -7
  34. package/dist/http/proxy.d.ts.map +1 -1
  35. package/dist/http/proxy.js +0 -7
  36. package/dist/realtime/sse.d.ts +0 -2
  37. package/dist/realtime/sse.d.ts.map +1 -1
  38. package/dist/realtime/sse.js +1 -2
  39. package/dist/realtime/sse_constants.d.ts +17 -0
  40. package/dist/realtime/sse_constants.d.ts.map +1 -0
  41. package/dist/realtime/sse_constants.js +16 -0
  42. package/dist/testing/CLAUDE.md +6 -3
  43. package/dist/testing/admin_integration.d.ts.map +1 -1
  44. package/dist/testing/admin_integration.js +1 -1
  45. package/dist/testing/app_server.d.ts +0 -15
  46. package/dist/testing/app_server.d.ts.map +1 -1
  47. package/dist/testing/app_server.js +1 -15
  48. package/dist/testing/audit_completeness.d.ts.map +1 -1
  49. package/dist/testing/audit_completeness.js +1 -1
  50. package/dist/testing/cross_backend/account_lifecycle.d.ts +5 -5
  51. package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -1
  52. package/dist/testing/cross_backend/account_lifecycle.js +1 -1
  53. package/dist/testing/cross_backend/actor_lookup.d.ts +5 -5
  54. package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -1
  55. package/dist/testing/cross_backend/actor_search.d.ts +3 -3
  56. package/dist/testing/cross_backend/actor_search.d.ts.map +1 -1
  57. package/dist/testing/cross_backend/app_settings.d.ts +3 -3
  58. package/dist/testing/cross_backend/app_settings.d.ts.map +1 -1
  59. package/dist/testing/cross_backend/body_size.d.ts +10 -0
  60. package/dist/testing/cross_backend/body_size.d.ts.map +1 -0
  61. package/dist/testing/cross_backend/body_size.js +137 -0
  62. package/dist/testing/cross_backend/body_size_smuggling.d.ts +10 -0
  63. package/dist/testing/cross_backend/body_size_smuggling.d.ts.map +1 -0
  64. package/dist/testing/cross_backend/body_size_smuggling.js +138 -0
  65. package/dist/testing/cross_backend/cell_cross_helpers.d.ts +0 -11
  66. package/dist/testing/cross_backend/cell_cross_helpers.d.ts.map +1 -1
  67. package/dist/testing/cross_backend/cell_crud.d.ts +2 -2
  68. package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -1
  69. package/dist/testing/cross_backend/cell_crud.js +1 -1
  70. package/dist/testing/cross_backend/cell_grant_role.d.ts +2 -2
  71. package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -1
  72. package/dist/testing/cross_backend/cell_grant_role.js +1 -1
  73. package/dist/testing/cross_backend/cell_relations.d.ts +2 -2
  74. package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -1
  75. package/dist/testing/cross_backend/cell_relations.js +1 -1
  76. package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -1
  77. package/dist/testing/cross_backend/conformance_table.js +8 -6
  78. package/dist/testing/cross_backend/fact_serving.d.ts +2 -3
  79. package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -1
  80. package/dist/testing/cross_backend/in_process_setup.d.ts +143 -0
  81. package/dist/testing/cross_backend/in_process_setup.d.ts.map +1 -0
  82. package/dist/testing/cross_backend/in_process_setup.js +166 -0
  83. package/dist/testing/cross_backend/origin.d.ts +5 -5
  84. package/dist/testing/cross_backend/origin.d.ts.map +1 -1
  85. package/dist/testing/cross_backend/ready.d.ts +2 -7
  86. package/dist/testing/cross_backend/ready.d.ts.map +1 -1
  87. package/dist/testing/cross_backend/setup.d.ts +54 -135
  88. package/dist/testing/cross_backend/setup.d.ts.map +1 -1
  89. package/dist/testing/cross_backend/setup.js +11 -171
  90. package/dist/testing/cross_backend/sse_round_trip.js +1 -1
  91. package/dist/testing/cross_backend/testing_backdoor.d.ts +2 -2
  92. package/dist/testing/cross_backend/testing_backdoor.d.ts.map +1 -1
  93. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  94. package/dist/testing/cross_backend/testing_reset_actions.js +2 -1
  95. package/dist/testing/integration.js +2 -2
  96. package/dist/testing/middleware.d.ts.map +1 -1
  97. package/dist/testing/middleware.js +2 -1
  98. package/dist/testing/sse_round_trip.d.ts +1 -1
  99. package/dist/testing/sse_round_trip.d.ts.map +1 -1
  100. package/dist/testing/sse_round_trip.js +1 -1
  101. package/dist/testing/stubs.d.ts.map +1 -1
  102. package/dist/testing/stubs.js +7 -9
  103. package/dist/testing/test_credentials.d.ts +23 -0
  104. package/dist/testing/test_credentials.d.ts.map +1 -0
  105. package/dist/testing/test_credentials.js +22 -0
  106. package/package.json +2 -2
@@ -0,0 +1,138 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * Cross-backend request-smuggling probe for the body-size limit's connection
4
+ * handling — the security sibling of `body_size.ts`.
5
+ *
6
+ * When the server caps the request body it answers `413` on the
7
+ * `Content-Length` header and closes the connection *without reading the
8
+ * oversized body*. That close is load-bearing: HTTP/1.1 forbids reusing a
9
+ * keep-alive connection whose request body wasn't consumed, because the unread
10
+ * body bytes would otherwise be parsed as the start of the next request — a
11
+ * classic request-smuggling vector. This suite proves the mitigation holds end
12
+ * to end by **pipelining**: it opens a raw TCP socket and sends, in one write,
13
+ * an oversized `POST` immediately followed by a second `GET` request. A correct
14
+ * server rejects the POST with `413` and never processes the trailing GET (the
15
+ * connection closes with the GET bytes unconsumed); a vulnerable one would
16
+ * drain past the body and answer the smuggled GET too. The assertion is
17
+ * therefore "**at most one** HTTP response comes back" — a *second* response is
18
+ * the smuggle. It's `<= 1` rather than "exactly the 413" because the impls close
19
+ * differently at the TCP level (node-server graceful close delivers the 413
20
+ * first; hyper's RST can drop the in-flight 413 before the client reads it), so
21
+ * demanding a cleanly-read 413 would be flaky; the 413-ness itself is pinned
22
+ * reliably over `fetch` by `describe_body_size_cross_tests`. A **positive
23
+ * control** (two pipelined requests → >= 2 responses) proves a second response
24
+ * *would* be seen if the trailing request were processed — without it the
25
+ * `<= 1` assertion would be vacuous on a server that never reuses connections,
26
+ * and it also proves the response counter isn't undercounting.
27
+ *
28
+ * Raw-socket by necessity (the `FetchTransport` can't pipeline two requests on
29
+ * one connection), so — unlike `body_size.ts` — this is **cross-process only**
30
+ * (no in-process leg; there is no socket in-process) and ungated (the limit is
31
+ * on every spine). Robust by construction: it counts responses until the
32
+ * socket closes or a short read timeout, so a server that closes (the expected
33
+ * path) and one that merely holds the connection open without smuggling both
34
+ * read as one response; only an actual second response fails.
35
+ *
36
+ * Cited property: `docs/security.md` §"Body Size Limiting" (connection close on
37
+ * oversized reject).
38
+ *
39
+ * `$lib`-free by contract (relative + `node:` specifiers only).
40
+ *
41
+ * @module
42
+ */
43
+ import { connect } from 'node:net';
44
+ import { describe, test, assert } from 'vitest';
45
+ import { SPINE_RPC_PATH } from './default_spine_surface.js';
46
+ /** The shared 1 MiB cap (see `body_size.ts` for why it's a local constant). */
47
+ const BODY_LIMIT_DEFAULT_BYTES = 1024 * 1024;
48
+ /**
49
+ * Open a raw TCP socket to `base_url`, write `request_bytes` once, and collect
50
+ * everything the server sends back until it closes the connection or
51
+ * `read_timeout_ms` elapses. Write errors are swallowed: the server closing
52
+ * mid-upload (the correct response to an oversized body) surfaces as
53
+ * `EPIPE`/`ECONNRESET` on our unfinished write, which is exactly the behavior
54
+ * under test — what matters is what we *read back*.
55
+ */
56
+ const send_raw = (base_url, request_bytes, read_timeout_ms) => new Promise((resolve) => {
57
+ const url = new URL(base_url);
58
+ const port = Number(url.port) || (url.protocol === 'https:' ? 443 : 80);
59
+ const socket = connect({ host: url.hostname, port });
60
+ let received = '';
61
+ let settled = false;
62
+ const finish = () => {
63
+ if (settled)
64
+ return;
65
+ settled = true;
66
+ clearTimeout(timer);
67
+ socket.destroy();
68
+ resolve(received);
69
+ };
70
+ const timer = setTimeout(finish, read_timeout_ms);
71
+ socket.setEncoding('latin1');
72
+ socket.on('connect', () => socket.write(request_bytes));
73
+ socket.on('data', (chunk) => {
74
+ received += chunk;
75
+ });
76
+ socket.on('error', () => { }); // EPIPE/ECONNRESET on mid-write close is expected
77
+ socket.on('close', finish);
78
+ });
79
+ /**
80
+ * Count HTTP response status lines in a raw byte stream. Deliberately
81
+ * **unanchored** (no `^`/`m`): a second pipelined response is concatenated
82
+ * straight after the first response's body, which carries no trailing newline,
83
+ * so a line-anchored match would miss it — and missing a smuggled second
84
+ * response is a silent false negative. No response header or JSON error body
85
+ * contains the literal `HTTP/1.x NNN`, so an unanchored match counts exactly
86
+ * the status lines. The positive control below proves this counts 2 when the
87
+ * server genuinely returns 2.
88
+ */
89
+ const count_responses = (raw) => (raw.match(/HTTP\/1\.[01] \d{3}/g) ?? []).length;
90
+ export const describe_body_size_smuggling_cross_tests = (options) => {
91
+ const { base_url } = options;
92
+ const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
93
+ const host = new URL(base_url).host;
94
+ describe('body-size limit — request-smuggling resistance', () => {
95
+ // Positive control: prove the server returns >1 response on a single
96
+ // connection. Without this the smuggling assertion below would be
97
+ // vacuously green on a server that simply never reuses connections — and
98
+ // it also validates `count_responses` actually counts a second response.
99
+ test('control: two pipelined requests → ≥2 responses (connection reuse is real)', async () => {
100
+ const two_requests = `GET ${rpc_path} HTTP/1.1\r\nHost: ${host}\r\n\r\n` +
101
+ `GET ${rpc_path} HTTP/1.1\r\nHost: ${host}\r\nConnection: close\r\n\r\n`;
102
+ const response = await send_raw(base_url, two_requests, 2000);
103
+ const n = count_responses(response);
104
+ assert.ok(n >= 2, `expected ≥2 responses on one keep-alive connection (got ${n}); without ` +
105
+ `connection reuse — or with an undercounting matcher — the smuggling ` +
106
+ `assertion below is not a real signal. Raw head: ${response.slice(0, 120)}`);
107
+ });
108
+ test('oversized POST + pipelined GET → smuggled request not processed', async () => {
109
+ const oversized_len = BODY_LIMIT_DEFAULT_BYTES + 1024;
110
+ // One write: an over-cap POST (rejected on Content-Length, body never
111
+ // read) immediately followed by a GET. If the server wrongly drained
112
+ // the unread body it would reach + answer this GET — a smuggle.
113
+ const payload = `POST ${rpc_path} HTTP/1.1\r\n` +
114
+ `Host: ${host}\r\n` +
115
+ `Content-Type: application/json\r\n` +
116
+ `Content-Length: ${oversized_len}\r\n` +
117
+ `\r\n` +
118
+ 'x'.repeat(oversized_len) +
119
+ `GET ${rpc_path} HTTP/1.1\r\nHost: ${host}\r\n\r\n`;
120
+ const response = await send_raw(base_url, payload, 2000);
121
+ // The security property is "the pipelined GET is not processed", i.e.
122
+ // **at most one** response comes back (the 413, or none if the close
123
+ // raced the read). A *second* response is the smuggle. We assert `<= 1`
124
+ // rather than "exactly the 413" because the two impls close the
125
+ // connection differently at the TCP level — node-server closes
126
+ // gracefully (the 413 is delivered first), hyper sends an RST that can
127
+ // drop the in-flight 413 before the client reads it — and demanding a
128
+ // cleanly-read 413 here would be flaky. That the oversized body is
129
+ // rejected *with* a 413 is pinned reliably (over `fetch`) by
130
+ // `describe_body_size_cross_tests`; this test owns only the no-smuggle
131
+ // half. The control above proves a second response *would* be seen if
132
+ // the GET were processed, so `<= 1` is a real signal, not a vacuous one.
133
+ const n = count_responses(response);
134
+ assert.ok(n <= 1, `expected at most one response (the GET must not be smuggled in off the ` +
135
+ `unread body); a second response means it was. Saw ${n}. Raw head: ${response.slice(0, 120)}`);
136
+ });
137
+ });
138
+ };
@@ -1,17 +1,6 @@
1
1
  import '../assert_dev_env.js';
2
2
  import type { z } from 'zod';
3
3
  import type { FetchTransport } from '../transports/fetch_transport.js';
4
- import type { BackendCapabilities } from './capabilities.js';
5
- import type { SetupTest } from './setup.js';
6
- /** Shared options for the cell cross-backend parity suites. */
7
- export interface CellCrossTestOptions {
8
- /** Per-test fixture-producing function (fresh keeper + db per call). */
9
- readonly setup_test: SetupTest;
10
- /** Backend capability declarations — each suite gates on its own flag. */
11
- readonly capabilities: BackendCapabilities;
12
- /** RPC endpoint path the cell verbs are mounted on. Default `/api/rpc`. */
13
- readonly rpc_path?: string;
14
- }
15
4
  /** Minimal JSON-RPC envelope shape the suites read off responses. */
16
5
  export interface RpcResult {
17
6
  readonly ok: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"cell_cross_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_cross_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAmB9B,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAG3B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,kCAAkC,CAAC;AACrE,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,YAAY,CAAC;AAE1C,+DAA+D;AAC/D,MAAM,WAAW,oBAAoB;IACpC,wEAAwE;IACxE,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,0EAA0E;IAC1E,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,2EAA2E;IAC3E,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAC,CAAC;CAC5F;AAED;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAC1B,WAAW,cAAc,EACzB,MAAM,MAAM,EACZ,QAAQ,MAAM,EACd,QAAQ,OAAO,EACf,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC7B,OAAO,CAAC,SAAS,CAMnB,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,YAAY,GAAI,GAAG,SAAS,KAAG,OAG/B,CAAC;AAEd;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAAE,GAAG,SAAS,EAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAG,CAQrE,CAAC"}
1
+ {"version":3,"file":"cell_cross_helpers.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/cell_cross_helpers.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAmB9B,OAAO,KAAK,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAG3B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,kCAAkC,CAAC;AAErE,qEAAqE;AACrE,MAAM,WAAW,SAAS;IACzB,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE;QAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;KAAC,CAAC;CAC5F;AAED;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAC1B,WAAW,cAAc,EACzB,MAAM,MAAM,EACZ,QAAQ,MAAM,EACd,QAAQ,OAAO,EACf,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,KAC7B,OAAO,CAAC,SAAS,CAMnB,CAAC;AAEF,gFAAgF;AAChF,eAAO,MAAM,YAAY,GAAI,GAAG,SAAS,KAAG,OAG/B,CAAC;AAEd;;;GAGG;AACH,eAAO,MAAM,aAAa,GAAI,CAAC,EAAE,GAAG,SAAS,EAAE,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,KAAG,CAQrE,CAAC"}
@@ -1,4 +1,4 @@
1
1
  import '../assert_dev_env.js';
2
- import { type CellCrossTestOptions } from './cell_cross_helpers.js';
3
- export declare const describe_cell_crud_cross_tests: (options: CellCrossTestOptions) => void;
2
+ import type { RpcPathCrossSuiteOptions } from './setup.js';
3
+ export declare const describe_cell_crud_cross_tests: (options: RpcPathCrossSuiteOptions) => 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;AAqD9B,OAAO,EAIN,KAAK,oBAAoB,EACzB,MAAM,yBAAyB,CAAC;AAGjC,eAAO,MAAM,8BAA8B,GAAI,SAAS,oBAAoB,KAAG,IAwS9E,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;AAsD9B,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,YAAY,CAAC;AAGzD,eAAO,MAAM,8BAA8B,GAAI,SAAS,wBAAwB,KAAG,IAwSlF,CAAC"}
@@ -42,7 +42,7 @@ import '../assert_dev_env.js';
42
42
  import { describe, assert } from 'vitest';
43
43
  import { CellCreateOutput, CellDeleteOutput, CellGetOutput, CellListOutput, CellUpdateOutput, } from '../../auth/cell_action_specs.js';
44
44
  import { test_if } from './capabilities.js';
45
- import { cross_rpc_call, error_reason, expect_output, } from './cell_cross_helpers.js';
45
+ import { cross_rpc_call, error_reason, expect_output } from './cell_cross_helpers.js';
46
46
  import { SPINE_RPC_PATH } from './default_spine_surface.js';
47
47
  export const describe_cell_crud_cross_tests = (options) => {
48
48
  const { setup_test, capabilities } = options;
@@ -1,8 +1,8 @@
1
1
  import '../assert_dev_env.js';
2
- import { type CellCrossTestOptions } from './cell_cross_helpers.js';
2
+ import type { RpcPathCrossSuiteOptions } 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: CellCrossTestOptions) => void;
7
+ export declare const describe_cell_grant_role_cross_tests: (options: RpcPathCrossSuiteOptions) => 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;AA6C9B,OAAO,EACN,KAAK,oBAAoB,EAIzB,MAAM,yBAAyB,CAAC;AAGjC,+EAA+E;AAC/E,eAAO,MAAM,gBAAgB,gBAAyB,CAAC;AAEvD,oFAAoF;AACpF,eAAO,MAAM,yBAAyB,qBAAqB,CAAC;AAK5D,eAAO,MAAM,oCAAoC,GAAI,SAAS,oBAAoB,KAAG,IAuIpF,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;AA8C9B,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,YAAY,CAAC;AAGzD,+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,IAuIxF,CAAC"}
@@ -37,7 +37,7 @@ import { describe, assert } from 'vitest';
37
37
  import { CellCreateOutput, CellGetOutput, CellUpdateOutput } from '../../auth/cell_action_specs.js';
38
38
  import { CellGrantCreateOutput, ERROR_CELL_GRANT_UNKNOWN_ROLE, } from '../../auth/cell_grant_action_specs.js';
39
39
  import { test_if } from './capabilities.js';
40
- import { cross_rpc_call, error_reason, expect_output, } from './cell_cross_helpers.js';
40
+ import { cross_rpc_call, error_reason, expect_output } from './cell_cross_helpers.js';
41
41
  import { SPINE_CELL_EDITOR_ROLE, SPINE_RPC_PATH } from './default_spine_surface.js';
42
42
  /** App role the holder is seeded with; matches the spine's registered role. */
43
43
  export const CELL_EDITOR_ROLE = SPINE_CELL_EDITOR_ROLE;
@@ -1,4 +1,4 @@
1
1
  import '../assert_dev_env.js';
2
- import { type CellCrossTestOptions } from './cell_cross_helpers.js';
3
- export declare const describe_cell_relations_cross_tests: (options: CellCrossTestOptions) => void;
2
+ import type { RpcPathCrossSuiteOptions } from './setup.js';
3
+ export declare const describe_cell_relations_cross_tests: (options: RpcPathCrossSuiteOptions) => 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;AAoF9B,OAAO,EAIN,KAAK,oBAAoB,EACzB,MAAM,yBAAyB,CAAC;AAsFjC,eAAO,MAAM,mCAAmC,GAAI,SAAS,oBAAoB,KAAG,IAyvBnF,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;AAqF9B,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,YAAY,CAAC;AAsFzD,eAAO,MAAM,mCAAmC,GAAI,SAAS,wBAAwB,KAAG,IAyvBvF,CAAC"}
@@ -60,7 +60,7 @@ import { CellFieldDeleteOutput, CellFieldListOutput, CellFieldSetOutput, } from
60
60
  import { CellItemDeleteOutput, CellItemInsertOutput, CellItemListOutput, CellItemMoveOutput, } from '../../auth/cell_item_action_specs.js';
61
61
  import { CellAuditListOutput } from '../../auth/cell_audit_action_specs.js';
62
62
  import { test_if } from './capabilities.js';
63
- import { cross_rpc_call, error_reason, expect_output, } from './cell_cross_helpers.js';
63
+ import { cross_rpc_call, error_reason, expect_output } from './cell_cross_helpers.js';
64
64
  import { SPINE_RPC_PATH } from './default_spine_surface.js';
65
65
  /** Create a cell over the wire and return its id (the parity gate parses the output). */
66
66
  const create_cell = async (t, rpc_path, h, params) => expect_output(await cross_rpc_call(t, rpc_path, 'cell_create', params, h), CellCreateOutput).cell
@@ -1 +1 @@
1
- {"version":3,"file":"conformance_table.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_table.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAwB9B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAE1D,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AAMjE,OAAO,EAIN,KAAK,uBAAuB,EAC5B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAC,KAAK,eAAe,EAA4B,MAAM,uBAAuB,CAAC;AACtF,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAC,SAAS,EAAc,MAAM,YAAY,CAAC;AAGvD;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B;IAC1C,iEAAiE;IACjE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,gEAAgE;IAChE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,sDAAsD;AACtD,MAAM,WAAW,uBAAuB;IACvC,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC/C,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,0EAA0E;IAC1E,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,kEAAkE;IAClE,QAAQ,CAAC,aAAa,EAAE,uBAAuB,CAAC;IAChD,4EAA4E;IAC5E,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,CAAC,EAAE,0BAA0B,CAAC;IACjD,iEAAiE;IACjE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAgOD;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,GAAI,SAAS,uBAAuB,KAAG,IAgBnF,CAAC"}
1
+ {"version":3,"file":"conformance_table.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_table.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAwB9B,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAE1D,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AAMjE,OAAO,EAIN,KAAK,uBAAuB,EAC5B,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAC,KAAK,eAAe,EAA4B,MAAM,uBAAuB,CAAC;AACtF,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,KAAK,EAAC,SAAS,EAAc,MAAM,YAAY,CAAC;AAGvD;;;;;GAKG;AACH,MAAM,WAAW,0BAA0B;IAC1C,iEAAiE;IACjE,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,gEAAgE;IAChE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,sDAAsD;AACtD,MAAM,WAAW,uBAAuB;IACvC,8CAA8C;IAC9C,QAAQ,CAAC,KAAK,EAAE,aAAa,CAAC,eAAe,CAAC,CAAC;IAC/C,+DAA+D;IAC/D,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC;IAC/B,oEAAoE;IACpE,QAAQ,CAAC,cAAc,EAAE,cAAc,CAAC;IACxC,0EAA0E;IAC1E,QAAQ,CAAC,YAAY,EAAE,mBAAmB,CAAC;IAC3C,kEAAkE;IAClE,QAAQ,CAAC,aAAa,EAAE,uBAAuB,CAAC;IAChD,4EAA4E;IAC5E,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD,4EAA4E;IAC5E,QAAQ,CAAC,UAAU,CAAC,EAAE,0BAA0B,CAAC;IACjD,iEAAiE;IACjE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;CAC7B;AAkOD;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,GAAI,SAAS,uBAAuB,KAAG,IAgBnF,CAAC"}
@@ -133,13 +133,15 @@ const run_rpc_case = async (c, transport, headers, suppress_default_origin, reso
133
133
  assert.ok(!res.ok, `${c.name}: expected error status ${c.expect.status} but got success`);
134
134
  assert.strictEqual(res.status, c.expect.status, `${c.name}: error status`);
135
135
  if (c.expect.error_reason !== undefined) {
136
+ // A row that *declares* a reason must carry it — present AND equal —
137
+ // mirroring the REST branch's unconditional `body.error` assertion. The
138
+ // earlier skip-if-absent form let a backend that dropped the reason pass
139
+ // a row declaring one, blessing a reason/forensic-parity divergence (the
140
+ // IDOR-mask / privilege reason is exactly the distinguishing bit). Rows
141
+ // whose denial genuinely has no reason (the bare `unauthenticated()` 401)
142
+ // simply omit `error_reason` and are pinned by `status` above.
136
143
  const reason = res.error.data?.reason;
137
- // Most RPC denials carry `error.data.reason` (incl. the pre-validation
138
- // 401 now); a denial that genuinely omits it falls back to the status
139
- // assertion above to pin the denial class.
140
- if (reason !== undefined) {
141
- assert.strictEqual(reason, c.expect.error_reason, `${c.name}: error.data.reason`);
142
- }
144
+ assert.strictEqual(reason, c.expect.error_reason, `${c.name}: error.data.reason`);
143
145
  }
144
146
  if (c.expect.fields)
145
147
  assert_fields(res.error.data, c.expect.fields, c.name);
@@ -1,13 +1,12 @@
1
1
  import '../assert_dev_env.js';
2
- import { type CellCrossTestOptions } from './cell_cross_helpers.js';
3
- import type { SetupTest } from './setup.js';
2
+ import type { RpcPathCrossSuiteOptions, SetupTest } from './setup.js';
4
3
  /**
5
4
  * The fact suite adds one optional knob to the shared cell options: a setup
6
5
  * variant whose keeper carries a **second actor**. Only the multi-actor case
7
6
  * needs it; the rest of the suite runs single-actor, so wiring it is opt-in.
8
7
  * Omit it and the multi-actor case silently skips.
9
8
  */
10
- export interface FactServingCrossTestOptions extends CellCrossTestOptions {
9
+ export interface FactServingCrossTestOptions extends RpcPathCrossSuiteOptions {
11
10
  readonly setup_test_multi_actor?: SetupTest;
12
11
  }
13
12
  export declare const describe_fact_serving_cross_tests: (options: FactServingCrossTestOptions) => void;
@@ -1 +1 @@
1
- {"version":3,"file":"fact_serving.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/fact_serving.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA+C9B,OAAO,EAAgC,KAAK,oBAAoB,EAAC,MAAM,yBAAyB,CAAC;AAEjG,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,YAAY,CAAC;AAE1C;;;;;GAKG;AACH,MAAM,WAAW,2BAA4B,SAAQ,oBAAoB;IACxE,QAAQ,CAAC,sBAAsB,CAAC,EAAE,SAAS,CAAC;CAC5C;AA8BD,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IA8NxF,CAAC"}
1
+ {"version":3,"file":"fact_serving.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/fact_serving.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAiD9B,OAAO,KAAK,EAAC,wBAAwB,EAAE,SAAS,EAAC,MAAM,YAAY,CAAC;AAEpE;;;;;GAKG;AACH,MAAM,WAAW,2BAA4B,SAAQ,wBAAwB;IAC5E,QAAQ,CAAC,sBAAsB,CAAC,EAAE,SAAS,CAAC;CAC5C;AA8BD,eAAO,MAAM,iCAAiC,GAAI,SAAS,2BAA2B,KAAG,IA8NxF,CAAC"}
@@ -0,0 +1,143 @@
1
+ import '../assert_dev_env.js';
2
+ import type { RouteSpec } from '../../http/route_spec.js';
3
+ import type { AppSurfaceSpec } from '../../http/surface.js';
4
+ import type { BootstrapServerOptions } from '../../server/app_server.js';
5
+ import type { AppServerContext } from '../../server/app_server_context.js';
6
+ import type { SessionOptions } from '../../auth/session_cookie.js';
7
+ import { type CreateTestAppOptions, type SuiteAppOptions } from '../app_server.js';
8
+ import { type RpcEndpointsSuiteOption } from '../rpc_helpers.js';
9
+ import { type BackendCapabilities } from './capabilities.js';
10
+ import { type SetupTest, type ExtraAccountSpec } from './setup.js';
11
+ /**
12
+ * Options for `default_in_process_setup`. Extends `CreateTestAppOptions`
13
+ * with the same `extra_accounts` slot the cross-process variant accepts
14
+ * — both transports observe the same bootstrap-time secondary set so
15
+ * suite bodies can read `fixture.extra_accounts[username]` uniformly.
16
+ */
17
+ export interface InProcessSetupOptions extends CreateTestAppOptions {
18
+ /**
19
+ * Additional accounts seeded at this transport's bootstrap-equivalent
20
+ * step. See `ExtraAccountSpec` for the cradle-only-bypass rationale.
21
+ * Most suites pass `undefined` / `[]`; the `ROLE_KEEPER` probe (in
22
+ * `describe_standard_admin_integration_tests`) is the primary user.
23
+ */
24
+ readonly extra_accounts?: ReadonlyArray<ExtraAccountSpec>;
25
+ /**
26
+ * Additional actor names to seed on the bootstrapped keeper — exposed on
27
+ * `fixture.extra_actors`. See `CrossProcessSetupOptions.extra_actors` /
28
+ * `TestFixtureBase.extra_actors`. Seeded directly against the live backend
29
+ * DB (in-process has no wire hop).
30
+ */
31
+ readonly extra_actors?: ReadonlyArray<string>;
32
+ }
33
+ /**
34
+ * Build a `SetupTest` that creates a fresh `TestApp` per call via
35
+ * `create_test_app` and projects it into the `TestFixture` shape.
36
+ *
37
+ * Same factory inputs `create_test_app` already takes — this helper
38
+ * is a projection layer, not a new lifecycle. fuz_app's own `src/test/`
39
+ * and consumer suites pass `default_in_process_setup({...factory_inputs})`
40
+ * in place of the old per-suite factory-input bundle. The `extra_accounts`
41
+ * slot (see `InProcessSetupOptions`) seeds bootstrap-time secondaries
42
+ * directly via `create_test_account_with_credentials` against the same
43
+ * DB the keeper just landed on — mirrors the cross-process
44
+ * `_testing_reset` cradle so suite bodies read
45
+ * `fixture.extra_accounts[username]` uniformly regardless of transport.
46
+ *
47
+ * The describe-level `auth_integration_truncate_tables` / pglite WASM
48
+ * cache lifecycle stays in `create_pglite_factory` / `create_describe_db`
49
+ * (`testing/db.ts`) — `default_in_process_setup` doesn't manage db state
50
+ * beyond what `create_test_app` already does.
51
+ */
52
+ export declare const default_in_process_setup: (options: InProcessSetupOptions) => SetupTest;
53
+ /**
54
+ * Consumer-facing options for `default_in_process_suite_options` — the
55
+ * minimal factory inputs both `default_in_process_setup` and
56
+ * `create_test_app_surface_spec` consume to produce the
57
+ * `{setup_test, surface_source, capabilities}` bundle.
58
+ */
59
+ export interface DefaultInProcessSuiteOptions {
60
+ session_options: SessionOptions<string>;
61
+ create_route_specs: (ctx: AppServerContext) => Array<RouteSpec>;
62
+ rpc_endpoints?: RpcEndpointsSuiteOption;
63
+ /**
64
+ * Bootstrap config — top-level slot, single source of truth for both
65
+ * surface generation and live dispatch. Same precedent as
66
+ * `rpc_endpoints`. Discriminated by `mode`; omit for the default (no
67
+ * bootstrap route mounted).
68
+ */
69
+ bootstrap?: BootstrapServerOptions;
70
+ app_options?: SuiteAppOptions;
71
+ /**
72
+ * Additional roles to grant the bootstrapped keeper alongside
73
+ * `ROLE_KEEPER` — additive, never replaces. The keeper account
74
+ * always holds `ROLE_KEEPER` (otherwise daemon-token auth breaks);
75
+ * pass extras here for suites that need additional role coverage.
76
+ *
77
+ * Admin-suite consumers pass `[ROLE_ADMIN]` so the default keeper
78
+ * can hit admin-gated RPC methods.
79
+ * `describe_standard_admin_integration_tests` and
80
+ * `describe_audit_completeness_tests` need this.
81
+ */
82
+ extra_keeper_roles?: Array<string>;
83
+ /**
84
+ * Bootstrap-time secondary accounts seeded alongside the keeper. See
85
+ * `ExtraAccountSpec` for the cradle-only-bypass rationale. Same shape
86
+ * as the cross-process `extra_accounts` option — suites read seeded
87
+ * accounts from `fixture.extra_accounts[username]` regardless of
88
+ * transport.
89
+ */
90
+ extra_accounts?: ReadonlyArray<ExtraAccountSpec>;
91
+ /**
92
+ * Additional actor names to seed on the bootstrapped keeper — exposed on
93
+ * `fixture.extra_actors`. See `TestFixtureBase.extra_actors`.
94
+ */
95
+ extra_actors?: ReadonlyArray<string>;
96
+ /**
97
+ * Pre-built `AppSurfaceSpec` — overrides the default which calls
98
+ * `create_test_app_surface_spec` against the same factory inputs.
99
+ * Pass when surface assembly needs fields outside the shared subset
100
+ * (e.g. `env_schema`, `event_specs`, `ws_endpoints`, `transform_middleware`).
101
+ */
102
+ surface_source?: AppSurfaceSpec;
103
+ }
104
+ /**
105
+ * Build the full in-process suite bundle in a single helper invocation.
106
+ * Output covers `{setup_test, surface_source, capabilities}` plus every
107
+ * factory input the Tier 1 suites read at their top level
108
+ * (`session_options`, `create_route_specs`, `rpc_endpoints`) — so the
109
+ * call site spreads once and adds only suite-specific extras
110
+ * (`roles`, `skip_routes`, `input_overrides`, `db_factories`, ...).
111
+ *
112
+ * ```ts
113
+ * // Suite-extras-free call: helper output is the entire options bag.
114
+ * describe_round_trip_validation(default_in_process_suite_options({
115
+ * session_options,
116
+ * create_route_specs,
117
+ * rpc_endpoints: [rpc_endpoint_spec],
118
+ * }));
119
+ *
120
+ * // With suite-specific extras: spread and add.
121
+ * describe_standard_admin_integration_tests({
122
+ * ...default_in_process_suite_options({
123
+ * session_options, create_route_specs, rpc_endpoints,
124
+ * extra_keeper_roles: [ROLE_ADMIN],
125
+ * }),
126
+ * roles,
127
+ * });
128
+ * ```
129
+ *
130
+ * Suites that don't read `session_options` / `rpc_endpoints` at their
131
+ * top level (`round_trip`, `data_exposure`) accept the spread anyway —
132
+ * excess properties on spread sources aren't checked by TS, and the
133
+ * uniform shape keeps consumer call sites mechanical.
134
+ */
135
+ export declare const default_in_process_suite_options: <const O extends DefaultInProcessSuiteOptions>(options: O) => {
136
+ setup_test: SetupTest;
137
+ surface_source: AppSurfaceSpec;
138
+ capabilities: BackendCapabilities;
139
+ session_options: O["session_options"];
140
+ create_route_specs: O["create_route_specs"];
141
+ rpc_endpoints: O["rpc_endpoints"];
142
+ };
143
+ //# sourceMappingURL=in_process_setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"in_process_setup.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/in_process_setup.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAqB9B,OAAO,KAAK,EAAC,SAAS,EAAC,MAAM,0BAA0B,CAAC;AACxD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,uBAAuB,CAAC;AAC1D,OAAO,KAAK,EAAC,sBAAsB,EAAC,MAAM,4BAA4B,CAAC;AACvE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,oCAAoC,CAAC;AACzE,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AAGjE,OAAO,EAIN,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAGN,KAAK,uBAAuB,EAC5B,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAA0B,KAAK,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAEpF,OAAO,EAGN,KAAK,SAAS,EAEd,KAAK,gBAAgB,EACrB,MAAM,YAAY,CAAC;AAepB;;;;;GAKG;AACH,MAAM,WAAW,qBAAsB,SAAQ,oBAAoB;IAClE;;;;;OAKG;IACH,QAAQ,CAAC,cAAc,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IAC1D;;;;;OAKG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC9C;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,eAAO,MAAM,wBAAwB,GACnC,SAAS,qBAAqB,KAAG,SAqEjC,CAAC;AAEH;;;;;GAKG;AACH,MAAM,WAAW,4BAA4B;IAC5C,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACxC,kBAAkB,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,KAAK,CAAC,SAAS,CAAC,CAAC;IAChE,aAAa,CAAC,EAAE,uBAAuB,CAAC;IACxC;;;;;OAKG;IACH,SAAS,CAAC,EAAE,sBAAsB,CAAC;IACnC,WAAW,CAAC,EAAE,eAAe,CAAC;IAC9B;;;;;;;;;;OAUG;IACH,kBAAkB,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IACnC;;;;;;OAMG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC,gBAAgB,CAAC,CAAC;IACjD;;;OAGG;IACH,YAAY,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IACrC;;;;;OAKG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;CAChC;AAWD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,eAAO,MAAM,gCAAgC,GAAI,KAAK,CAAC,CAAC,SAAS,4BAA4B,EAC5F,SAAS,CAAC,KACR;IACF,UAAU,EAAE,SAAS,CAAC;IACtB,cAAc,EAAE,cAAc,CAAC;IAC/B,YAAY,EAAE,mBAAmB,CAAC;IAClC,eAAe,EAAE,CAAC,CAAC,iBAAiB,CAAC,CAAC;IACtC,kBAAkB,EAAE,CAAC,CAAC,oBAAoB,CAAC,CAAC;IAC5C,aAAa,EAAE,CAAC,CAAC,eAAe,CAAC,CAAC;CA2BjC,CAAC"}
@@ -0,0 +1,166 @@
1
+ import '../assert_dev_env.js';
2
+ import { ROLE_KEEPER } from '../../auth/role_schema.js';
3
+ import { query_create_actor } from '../../auth/account_queries.js';
4
+ import { create_test_app, create_test_account_with_credentials, mint_test_session, } from '../app_server.js';
5
+ import { create_test_app_surface_spec } from '../stubs.js';
6
+ import { http_transport, } from '../rpc_helpers.js';
7
+ import { in_process_capabilities } from './capabilities.js';
8
+ import { build_extra_account_fixture, EXPIRED_SESSION_OFFSET_SECONDS, } from './setup.js';
9
+ /**
10
+ * Wrap a Hono-style app into a `FetchTransport`-shaped object so the
11
+ * shared `TestFixtureBase.transport` type holds for both in-process and
12
+ * cross-process setups. In-process has no real cookie jar — the no-op
13
+ * `cookies()` returns `[]`; in-process tests build cookies via
14
+ * `fixture.create_session_headers()` instead.
15
+ */
16
+ const in_process_fetch_transport = (app) => {
17
+ const call = http_transport(app);
18
+ const transport = ((url, init) => call(url, init));
19
+ return Object.assign(transport, { cookies: () => [] });
20
+ };
21
+ /**
22
+ * Build a `SetupTest` that creates a fresh `TestApp` per call via
23
+ * `create_test_app` and projects it into the `TestFixture` shape.
24
+ *
25
+ * Same factory inputs `create_test_app` already takes — this helper
26
+ * is a projection layer, not a new lifecycle. fuz_app's own `src/test/`
27
+ * and consumer suites pass `default_in_process_setup({...factory_inputs})`
28
+ * in place of the old per-suite factory-input bundle. The `extra_accounts`
29
+ * slot (see `InProcessSetupOptions`) seeds bootstrap-time secondaries
30
+ * directly via `create_test_account_with_credentials` against the same
31
+ * DB the keeper just landed on — mirrors the cross-process
32
+ * `_testing_reset` cradle so suite bodies read
33
+ * `fixture.extra_accounts[username]` uniformly regardless of transport.
34
+ *
35
+ * The describe-level `auth_integration_truncate_tables` / pglite WASM
36
+ * cache lifecycle stays in `create_pglite_factory` / `create_describe_db`
37
+ * (`testing/db.ts`) — `default_in_process_setup` doesn't manage db state
38
+ * beyond what `create_test_app` already does.
39
+ */
40
+ export const default_in_process_setup = (options) => async () => {
41
+ // Per-test fresh db. When `options.migration_namespaces` is set,
42
+ // `create_test_app` provisions an auth+extras PGlite (e.g. the cell
43
+ // layer); otherwise the auth-only default. Either way the factory
44
+ // resets + re-migrates on each `create`, so the per-test keeper
45
+ // bootstrap below lands on a clean DB.
46
+ const test_app = await create_test_app(options);
47
+ // Seed bootstrap-time secondaries against the same DB the keeper
48
+ // just landed on. Direct-insert is the only path for roles whose
49
+ // `grant_paths` excludes `'admin'` (e.g. `ROLE_KEEPER`) — see
50
+ // `ExtraAccountSpec` for why this bypass is bootstrap-cradle-only.
51
+ const extra_accounts = {};
52
+ const { cookie_name } = options.session_options;
53
+ for (const spec of options.extra_accounts ?? []) {
54
+ const seeded = await create_test_account_with_credentials({
55
+ db: test_app.backend.deps.db,
56
+ keyring: test_app.backend.keyring,
57
+ session_options: options.session_options,
58
+ password: test_app.backend.deps.password,
59
+ username: spec.username,
60
+ password_value: spec.password_value,
61
+ roles: [...spec.roles],
62
+ });
63
+ extra_accounts[spec.username] = build_extra_account_fixture(seeded, cookie_name);
64
+ }
65
+ // Seed additional keeper actors directly against the same DB. Mirrors
66
+ // the cross-process `_testing_reset` `extra_actors` path; no production
67
+ // wire mints a second actor, so this bootstrap-cradle insert is the
68
+ // only way into a multi-actor keeper state.
69
+ const extra_actors = [];
70
+ for (const name of options.extra_actors ?? []) {
71
+ const seeded_actor = await query_create_actor({ db: test_app.backend.deps.db }, test_app.backend.account.id, name);
72
+ extra_actors.push({ id: seeded_actor.id, name: seeded_actor.name });
73
+ }
74
+ return {
75
+ transport: in_process_fetch_transport(test_app.app),
76
+ // In-process the wrapper is stateless and never auto-adds Origin —
77
+ // `options` is accepted for API symmetry with cross-process but
78
+ // has no observable effect.
79
+ fresh_transport: () => in_process_fetch_transport(test_app.app),
80
+ account: test_app.backend.account,
81
+ actor: test_app.backend.actor,
82
+ create_session_headers: test_app.create_session_headers,
83
+ create_bearer_headers: test_app.create_bearer_headers,
84
+ create_daemon_token_headers: test_app.create_daemon_token_headers,
85
+ create_account: test_app.create_account,
86
+ extra_accounts,
87
+ extra_actors,
88
+ // Forge directly against the live backend's DB + keyring — no wire
89
+ // hop needed in-process.
90
+ mint_expired_session: async () => {
91
+ const { session_cookie } = await mint_test_session({
92
+ db: test_app.backend.deps.db,
93
+ keyring: test_app.backend.keyring,
94
+ session_options: options.session_options,
95
+ account_id: test_app.backend.account.id,
96
+ expires_in_seconds: EXPIRED_SESSION_OFFSET_SECONDS,
97
+ });
98
+ return `${cookie_name}=${session_cookie}`;
99
+ },
100
+ };
101
+ };
102
+ // NOTE: bootstrap config is read from `options.bootstrap` — top-level slot,
103
+ // single source of truth for both the surface spec (so the route appears in
104
+ // `expected_public_routes` / attack-surface iteration) AND the live
105
+ // `create_test_app` (so the route exists at dispatch time and returns 403
106
+ // `ERROR_ALREADY_BOOTSTRAPPED` matching its declared 403 schema). Same
107
+ // precedent as `rpc_endpoints`. Discriminated union shape (`mode: 'disabled'`
108
+ // | `'surface_only'` | `'live'`) replaces the old `token_path: string | null`
109
+ // overload that conflated three deployment intents on one channel.
110
+ /**
111
+ * Build the full in-process suite bundle in a single helper invocation.
112
+ * Output covers `{setup_test, surface_source, capabilities}` plus every
113
+ * factory input the Tier 1 suites read at their top level
114
+ * (`session_options`, `create_route_specs`, `rpc_endpoints`) — so the
115
+ * call site spreads once and adds only suite-specific extras
116
+ * (`roles`, `skip_routes`, `input_overrides`, `db_factories`, ...).
117
+ *
118
+ * ```ts
119
+ * // Suite-extras-free call: helper output is the entire options bag.
120
+ * describe_round_trip_validation(default_in_process_suite_options({
121
+ * session_options,
122
+ * create_route_specs,
123
+ * rpc_endpoints: [rpc_endpoint_spec],
124
+ * }));
125
+ *
126
+ * // With suite-specific extras: spread and add.
127
+ * describe_standard_admin_integration_tests({
128
+ * ...default_in_process_suite_options({
129
+ * session_options, create_route_specs, rpc_endpoints,
130
+ * extra_keeper_roles: [ROLE_ADMIN],
131
+ * }),
132
+ * roles,
133
+ * });
134
+ * ```
135
+ *
136
+ * Suites that don't read `session_options` / `rpc_endpoints` at their
137
+ * top level (`round_trip`, `data_exposure`) accept the spread anyway —
138
+ * excess properties on spread sources aren't checked by TS, and the
139
+ * uniform shape keeps consumer call sites mechanical.
140
+ */
141
+ export const default_in_process_suite_options = (options) => ({
142
+ setup_test: default_in_process_setup({
143
+ session_options: options.session_options,
144
+ create_route_specs: options.create_route_specs,
145
+ rpc_endpoints: options.rpc_endpoints,
146
+ bootstrap: options.bootstrap,
147
+ app_options: options.app_options,
148
+ roles: [ROLE_KEEPER, ...(options.extra_keeper_roles ?? [])],
149
+ extra_accounts: options.extra_accounts,
150
+ extra_actors: options.extra_actors,
151
+ }),
152
+ surface_source: options.surface_source ??
153
+ create_test_app_surface_spec({
154
+ session_options: options.session_options,
155
+ create_route_specs: options.create_route_specs,
156
+ rpc_endpoints: options.rpc_endpoints,
157
+ // Mirror what `create_test_app` → `create_app_server` will mount.
158
+ // Both helpers read from the top-level `bootstrap` slot so surface
159
+ // and live app stay in sync by construction.
160
+ bootstrap: options.bootstrap,
161
+ }),
162
+ capabilities: in_process_capabilities,
163
+ session_options: options.session_options,
164
+ create_route_specs: options.create_route_specs,
165
+ rpc_endpoints: options.rpc_endpoints,
166
+ });
@@ -1,10 +1,10 @@
1
1
  import '../assert_dev_env.js';
2
- import type { CellCrossTestOptions } from './cell_cross_helpers.js';
2
+ import type { RpcPathCrossSuiteOptions } from './setup.js';
3
3
  /**
4
- * Options for the origin parity suite. Shares the shape of the cell /
5
- * account-lifecycle suites (`setup_test` / `capabilities` / `rpc_path`);
6
- * reuses `CellCrossTestOptions` rather than minting a structural duplicate.
4
+ * Options for the origin 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.
7
7
  */
8
- export type OriginCrossTestOptions = CellCrossTestOptions;
8
+ export type OriginCrossTestOptions = RpcPathCrossSuiteOptions;
9
9
  export declare const describe_origin_cross_tests: (options: OriginCrossTestOptions) => void;
10
10
  //# sourceMappingURL=origin.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"origin.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/origin.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAkC9B,OAAO,KAAK,EAAC,oBAAoB,EAAC,MAAM,yBAAyB,CAAC;AAGlE;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG,oBAAoB,CAAC;AAM1D,eAAO,MAAM,2BAA2B,GAAI,SAAS,sBAAsB,KAAG,IAwC7E,CAAC"}
1
+ {"version":3,"file":"origin.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/origin.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAkC9B,OAAO,KAAK,EAAC,wBAAwB,EAAC,MAAM,YAAY,CAAC;AAGzD;;;;GAIG;AACH,MAAM,MAAM,sBAAsB,GAAG,wBAAwB,CAAC;AAM9D,eAAO,MAAM,2BAA2B,GAAI,SAAS,sBAAsB,KAAG,IAwC7E,CAAC"}