@fuzdev/fuz_app 0.78.1 → 0.80.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 (91) hide show
  1. package/dist/actions/CLAUDE.md +5 -5
  2. package/dist/actions/action_rpc.d.ts +1 -1
  3. package/dist/actions/compile_action_registry.d.ts +1 -1
  4. package/dist/actions/compile_action_registry.js +1 -1
  5. package/dist/actions/connection_closer.d.ts +1 -1
  6. package/dist/actions/connection_closer.js +1 -1
  7. package/dist/auth/actor_lookup_action_specs.d.ts +1 -1
  8. package/dist/auth/actor_lookup_action_specs.js +1 -1
  9. package/dist/auth/actor_lookup_queries.d.ts +1 -1
  10. package/dist/auth/actor_lookup_queries.js +1 -1
  11. package/dist/auth/actor_search_actions.d.ts +1 -1
  12. package/dist/auth/actor_search_actions.js +1 -1
  13. package/dist/auth/actor_search_queries.d.ts +3 -3
  14. package/dist/auth/actor_search_queries.js +3 -3
  15. package/dist/auth/all_action_spec_registries.d.ts +1 -1
  16. package/dist/auth/all_action_spec_registries.js +1 -1
  17. package/dist/auth/cell_action_specs.d.ts +1 -1
  18. package/dist/auth/cell_action_specs.js +1 -1
  19. package/dist/auth/cell_actions.d.ts +2 -2
  20. package/dist/auth/cell_actions.js +2 -2
  21. package/dist/auth/cell_audit_events.d.ts +3 -3
  22. package/dist/auth/cell_audit_events.js +3 -3
  23. package/dist/auth/cell_data_schema.d.ts +2 -2
  24. package/dist/auth/cell_data_schema.js +2 -2
  25. package/dist/auth/cell_field_actions.d.ts +3 -3
  26. package/dist/auth/cell_field_actions.js +3 -3
  27. package/dist/auth/cell_grant_actions.d.ts +3 -3
  28. package/dist/auth/cell_grant_actions.js +3 -3
  29. package/dist/auth/cell_item_actions.d.ts +3 -3
  30. package/dist/auth/cell_item_actions.js +3 -3
  31. package/dist/db/cell_queries.d.ts +2 -2
  32. package/dist/db/cell_queries.js +1 -1
  33. package/dist/http/CLAUDE.md +4 -4
  34. package/dist/http/auth_shape.d.ts +2 -2
  35. package/dist/http/auth_shape.js +2 -2
  36. package/dist/http/ip_canonical.d.ts +1 -1
  37. package/dist/http/ip_canonical.js +1 -1
  38. package/dist/http/proxy.d.ts +1 -1
  39. package/dist/http/proxy.js +1 -1
  40. package/dist/http/route_spec.d.ts +1 -1
  41. package/dist/server/app_server_context.d.ts +1 -1
  42. package/dist/server/app_server_context.js +1 -1
  43. package/dist/testing/CLAUDE.md +6 -2
  44. package/dist/testing/audit_completeness.js +1 -1
  45. package/dist/testing/audit_drift_guard.d.ts +1 -1
  46. package/dist/testing/cross_backend/backend_config.d.ts +1 -1
  47. package/dist/testing/cross_backend/bench/run_cross_impl_bench.d.ts +1 -1
  48. package/dist/testing/cross_backend/capabilities.d.ts +10 -0
  49. package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
  50. package/dist/testing/cross_backend/capabilities.js +1 -0
  51. package/dist/testing/cross_backend/cell_cross_helpers.d.ts +1 -1
  52. package/dist/testing/cross_backend/cell_cross_helpers.js +2 -2
  53. package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
  54. package/dist/testing/cross_backend/default_backend_configs.js +2 -0
  55. package/dist/testing/cross_backend/default_secrets.d.ts +1 -1
  56. package/dist/testing/cross_backend/default_secrets.js +1 -1
  57. package/dist/testing/cross_backend/default_spine_surface.d.ts +1 -1
  58. package/dist/testing/cross_backend/default_spine_surface.js +1 -1
  59. package/dist/testing/cross_backend/fact_serving.d.ts +14 -0
  60. package/dist/testing/cross_backend/fact_serving.d.ts.map +1 -0
  61. package/dist/testing/cross_backend/fact_serving.js +189 -0
  62. package/dist/testing/cross_backend/setup.d.ts +3 -3
  63. package/dist/testing/cross_backend/setup.js +2 -2
  64. package/dist/testing/cross_backend/spawn_backend.d.ts +1 -1
  65. package/dist/testing/cross_backend/spawn_backend.js +1 -1
  66. package/dist/testing/cross_backend/testing_reset_actions.d.ts +34 -0
  67. package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
  68. package/dist/testing/cross_backend/testing_reset_actions.js +43 -0
  69. package/dist/testing/cross_backend/testing_server_bun.js +3 -3
  70. package/dist/testing/cross_backend/testing_server_core.d.ts +3 -3
  71. package/dist/testing/cross_backend/testing_server_deno.js +2 -2
  72. package/dist/testing/cross_backend/testing_server_node.js +1 -1
  73. package/dist/testing/cross_backend/ts_spine_backend_config.d.ts +1 -1
  74. package/dist/testing/cross_backend/ts_spine_backend_config.js +1 -1
  75. package/dist/testing/cross_backend/xfail.js +1 -1
  76. package/dist/testing/data_exposure.js +1 -1
  77. package/dist/testing/db_entities.d.ts +1 -1
  78. package/dist/testing/db_entities.js +3 -3
  79. package/dist/testing/integration.d.ts +1 -1
  80. package/dist/testing/role_grant_helpers.js +1 -1
  81. package/dist/testing/rpc_round_trip.js +1 -1
  82. package/dist/testing/schema_introspect.d.ts +3 -3
  83. package/dist/testing/schema_introspect.js +3 -3
  84. package/dist/testing/transports/sse_transport.js +2 -2
  85. package/dist/testing/transports/ws_client.d.ts +1 -1
  86. package/dist/testing/transports/ws_client.js +2 -2
  87. package/dist/testing/transports/ws_transport.js +1 -1
  88. package/dist/ui/CLAUDE.md +1 -1
  89. package/dist/ui/sidebar_state.svelte.d.ts +2 -2
  90. package/dist/ui/sidebar_state.svelte.js +2 -2
  91. package/package.json +1 -1
@@ -851,7 +851,7 @@ source of truth for wire-shape conformance.
851
851
 
852
852
  - `testing/cross_backend/capabilities.ts` — `BackendCapabilities` vocabulary
853
853
  (`bearer_auth` / `trusted_proxy` / `login_rate_limit` / `ws` / `sse` /
854
- `cell_crud` / `cell_relations` / `account_lifecycle`),
854
+ `cell_crud` / `cell_relations` / `account_lifecycle` / `fact_serving`),
855
855
  `test_if(cond, name, fn)`
856
856
  for capability-gated cases, and `in_process_capabilities` preset. `cell_crud`
857
857
  gates the CRUD parity suite, `cell_relations` the relation / ACL / audit
@@ -860,7 +860,11 @@ source of truth for wire-shape conformance.
860
860
  plain CRUD would declare `cell_crud: true, cell_relations: false`.
861
861
  `account_lifecycle` gates `describe_account_lifecycle_cross_tests` (the
862
862
  `account_delete` / `account_undelete` / `account_purge` parity suite) — also
863
- off the declared surface like cells, `true` on every spine.
863
+ off the declared surface like cells, `true` on every spine. `fact_serving`
864
+ gates `describe_fact_serving_cross_tests` (the cell-scoped per-reference +
865
+ admin-only bare-hash fact-serving parity suite); like cells it stays off the
866
+ declared surface and is `true` on every spine that mounts the serve routes +
867
+ the `_testing_put_fact` seeder.
864
868
 
865
869
  ### `cross_backend/standard.ts` — `describe_standard_cross_process_tests`
866
870
 
@@ -13,7 +13,7 @@ import './assert_dev_env.js';
13
13
  * a secondary failure. `describe_rpc_round_trip_tests` covers that RPC
14
14
  * directly, so primary breakages localize there first. For *unit-level*
15
15
  * "did the handler emit?" assertions without the persistence path, use
16
- * `create_recording_audit_emitter` from `audit_drift_guard.ts` — that
16
+ * `create_recording_audit_emitter` from `testing/audit_drift_guard.ts` — that
17
17
  * captures emits before they hit DB or transport.
18
18
  *
19
19
  * Bootstrap is excluded because it requires filesystem token state that
@@ -25,7 +25,7 @@ export declare const install_audit_drift_guard: () => void;
25
25
  /**
26
26
  * Marker pushed into a shared sequence array by an emit-recording
27
27
  * `audit_factory`. Pair with `RecordedClose` from
28
- * `connection_closer_helpers.ts` to test close-vs-emit ordering at
28
+ * `testing/connection_closer_helpers.ts` to test close-vs-emit ordering at
29
29
  * handler call sites — see `create_emit_ordering_audit_factory` below.
30
30
  */
31
31
  export interface AuditEmitMarker {
@@ -105,7 +105,7 @@ export interface BackendConfig {
105
105
  readonly bootstrap: BackendBootstrapConfig;
106
106
  /**
107
107
  * Capabilities this backend supports — drives `test_if(capabilities.X, ...)`
108
- * gating in suite bodies. See `capabilities.ts` for the vocabulary and
108
+ * gating in suite bodies. See `testing/cross_backend/capabilities.ts` for the vocabulary and
109
109
  * existing flags.
110
110
  */
111
111
  readonly capabilities: BackendCapabilities;
@@ -11,7 +11,7 @@ import type { BenchScenario } from './scenario.js';
11
11
  * fuz_util's benchmark library is the engine — `Benchmark` runs each scenario
12
12
  * as a task and `BenchmarkResult.stats` carries the percentiles; this module
13
13
  * is the thin scenario→task→tagged-result adapter. Reporting (markdown,
14
- * TS-vs-Rust verdict, JSON artifact) lives in `bench_report.ts`.
14
+ * TS-vs-Rust verdict, JSON artifact) lives in `testing/cross_backend/bench/bench_report.ts`.
15
15
  *
16
16
  * @module
17
17
  */
@@ -66,6 +66,16 @@ export interface BackendCapabilities {
66
66
  * this flag opts a backend into the lifecycle parity coverage.
67
67
  */
68
68
  readonly account_lifecycle: boolean;
69
+ /**
70
+ * The cell-gated fact-serving routes (`GET /api/cells/:cell_id/facts/:hash`
71
+ * + the admin-only `GET /api/facts/:hash`) are live-mounted on the backend,
72
+ * its DB carries the `fuz_facts` migration namespace, and it registers the
73
+ * `_testing_put_fact` seeder. Gates `describe_fact_serving_cross_tests` —
74
+ * the per-reference read model (cell-scoped admit via a viewable cell,
75
+ * cross-owner-dedup-no-leak, 404-mask, bare-hash admin-only). Like cells,
76
+ * the serve routes stay off the standard declared surface.
77
+ */
78
+ readonly fact_serving: boolean;
69
79
  }
70
80
  /**
71
81
  * Capability declarations for the in-process Hono transport. Every flag
@@ -1 +1 @@
1
- {"version":3,"file":"capabilities.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAmB9B;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;OAQG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;CACpC;AAED;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBASpC,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,EAAE,IAAI,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAG,IAMrF,CAAC"}
1
+ {"version":3,"file":"capabilities.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAmB9B;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IACnC;;;;OAIG;IACH,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,aAAa,EAAE,OAAO,CAAC;IAChC;;;OAGG;IACH,QAAQ,CAAC,gBAAgB,EAAE,OAAO,CAAC;IACnC;;;;OAIG;IACH,QAAQ,CAAC,EAAE,EAAE,OAAO,CAAC;IACrB;;;;;OAKG;IACH,QAAQ,CAAC,GAAG,EAAE,OAAO,CAAC;IACtB;;;;;;;OAOG;IACH,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC;;;;;;;;OAQG;IACH,QAAQ,CAAC,iBAAiB,EAAE,OAAO,CAAC;IACpC;;;;;;;;OAQG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC;CAC/B;AAED;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBAUpC,CAAC;AAEH;;;;;;;;GAQG;AACH,eAAO,MAAM,OAAO,GAAI,MAAM,OAAO,EAAE,MAAM,MAAM,EAAE,IAAI,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAG,IAMrF,CAAC"}
@@ -29,6 +29,7 @@ export const in_process_capabilities = Object.freeze({
29
29
  cell_crud: true,
30
30
  cell_relations: true,
31
31
  account_lifecycle: true,
32
+ fact_serving: true,
32
33
  });
33
34
  /**
34
35
  * Conditional `test()` wrapper — registers a vitest case only when
@@ -24,7 +24,7 @@ export interface RpcResult {
24
24
  }
25
25
  /**
26
26
  * POST a JSON-RPC call over a cross-process `FetchTransport` with the given
27
- * auth headers. Distinct from `rpc_helpers.ts`'s `app`-based `rpc_call`: this
27
+ * auth headers. Distinct from `testing/rpc_helpers.ts`'s `app`-based `rpc_call`: this
28
28
  * variant drives the cookie-jar `FetchTransport` the cross-backend harness
29
29
  * spawns against, and returns the slim `RpcResult` the cell suites read.
30
30
  */
@@ -1,7 +1,7 @@
1
1
  import '../assert_dev_env.js';
2
2
  /**
3
3
  * Shared call-site primitives for the cell cross-backend parity suites
4
- * (`cell_crud.ts` + `cell_relations.ts`).
4
+ * (`testing/cross_backend/cell_crud.ts` + `testing/cross_backend/cell_relations.ts`).
5
5
  *
6
6
  * The cell verbs are stateful and authz-shaped, so both suites POST raw
7
7
  * JSON-RPC envelopes (threading ids + auth headers across calls) and parse
@@ -18,7 +18,7 @@ import { assert } from 'vitest';
18
18
  import { create_rpc_post_init } from '../rpc_helpers.js';
19
19
  /**
20
20
  * POST a JSON-RPC call over a cross-process `FetchTransport` with the given
21
- * auth headers. Distinct from `rpc_helpers.ts`'s `app`-based `rpc_call`: this
21
+ * auth headers. Distinct from `testing/rpc_helpers.ts`'s `app`-based `rpc_call`: this
22
22
  * variant drives the cookie-jar `FetchTransport` the cross-backend harness
23
23
  * spawns against, and returns the slim `RpcResult` the cell suites read.
24
24
  */
@@ -1 +1 @@
1
- {"version":3,"file":"default_backend_configs.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_backend_configs.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAC,sBAAsB,EAAE,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAC/E,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAA2B,KAAK,gBAAgB,EAAC,MAAM,+BAA+B,CAAC;AAQ9F;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBASpC,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,EAAE,mBAStC,CAAC;AAeH,MAAM,WAAW,iCAAiC;IACjD,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C,oDAAoD;IACpD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD,6CAA6C;IAC7C,QAAQ,CAAC,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,iCAAiC,KACrC,aAoCF,CAAC;AAEF,MAAM,WAAW,mCAAmC;IACnD,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD,+CAA+C;IAC/C,QAAQ,CAAC,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,mCAAmC,KACvC,aA4CF,CAAC"}
1
+ {"version":3,"file":"default_backend_configs.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_backend_configs.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,KAAK,EAAC,sBAAsB,EAAE,aAAa,EAAC,MAAM,qBAAqB,CAAC;AAC/E,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAA2B,KAAK,gBAAgB,EAAC,MAAM,+BAA+B,CAAC;AAQ9F;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,EAAE,mBAUpC,CAAC;AAEH;;;;;GAKG;AACH,eAAO,MAAM,yBAAyB,EAAE,mBAUtC,CAAC;AAeH,MAAM,WAAW,iCAAiC;IACjD,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C,oDAAoD;IACpD,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD,6CAA6C;IAC7C,QAAQ,CAAC,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,iCAAiC,KACrC,aAoCF,CAAC;AAEF,MAAM,WAAW,mCAAmC;IACnD,gFAAgF;IAChF,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,sCAAsC;IACtC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,4DAA4D;IAC5D,QAAQ,CAAC,aAAa,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;IAC9C;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,iEAAiE;IACjE,QAAQ,CAAC,SAAS,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACtD,+CAA+C;IAC/C,QAAQ,CAAC,YAAY,CAAC,EAAE,mBAAmB,CAAC;IAC5C,wEAAwE;IACxE,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;IAClC,sEAAsE;IACtE,QAAQ,CAAC,mBAAmB,CAAC,EAAE,OAAO,CAAC,sBAAsB,CAAC,CAAC;IAC/D;;;;OAIG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAC3B;;;OAGG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;CAC9B;AAED;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,mCAAmC,KACvC,aA4CF,CAAC"}
@@ -16,6 +16,7 @@ export const ts_default_capabilities = Object.freeze({
16
16
  cell_crud: true,
17
17
  cell_relations: true,
18
18
  account_lifecycle: true,
19
+ fact_serving: true,
19
20
  });
20
21
  /**
21
22
  * Capabilities for the Rust family. Adds `trusted_proxy: true` (the
@@ -32,6 +33,7 @@ export const rust_default_capabilities = Object.freeze({
32
33
  cell_crud: true,
33
34
  cell_relations: true,
34
35
  account_lifecycle: true,
36
+ fact_serving: true,
35
37
  });
36
38
  /** Bootstrap block built from the default secrets + supplied paths. */
37
39
  const build_default_bootstrap = (paths, overrides) => ({
@@ -10,7 +10,7 @@ import '../assert_dev_env.js';
10
10
  * production load.
11
11
  *
12
12
  * Each constant is exported individually so consumers can override one
13
- * without re-deriving the rest. Builders in `default_backend_configs.ts`
13
+ * without re-deriving the rest. Builders in `testing/cross_backend/default_backend_configs.ts`
14
14
  * thread these defaults into the `BackendConfig.bootstrap` block and the
15
15
  * `SECRET_FUZ_COOKIE_KEYS` env entry; callers compose the
16
16
  * `bootstrap_overrides` knob when they need a non-default keeper.
@@ -10,7 +10,7 @@ import '../assert_dev_env.js';
10
10
  * production load.
11
11
  *
12
12
  * Each constant is exported individually so consumers can override one
13
- * without re-deriving the rest. Builders in `default_backend_configs.ts`
13
+ * without re-deriving the rest. Builders in `testing/cross_backend/default_backend_configs.ts`
14
14
  * thread these defaults into the `BackendConfig.bootstrap` block and the
15
15
  * `SECRET_FUZ_COOKIE_KEYS` env entry; callers compose the
16
16
  * `bootstrap_overrides` knob when they need a non-default keeper.
@@ -63,7 +63,7 @@ export interface SpineRpcEndpointsOptions {
63
63
  * closures are never called across the process boundary.
64
64
  *
65
65
  * Test binaries append their own `_testing_reset` action to this endpoint's
66
- * `actions` (see `testing_reset_actions.ts`); it is intentionally excluded
66
+ * `actions` (see `testing/cross_backend/testing_reset_actions.ts`); it is intentionally excluded
67
67
  * here so it stays off the declared surface (the harness calls it directly
68
68
  * over the daemon-token channel).
69
69
  *
@@ -71,7 +71,7 @@ export const SPINE_SSE_PATH = '/api/admin/audit/stream';
71
71
  * closures are never called across the process boundary.
72
72
  *
73
73
  * Test binaries append their own `_testing_reset` action to this endpoint's
74
- * `actions` (see `testing_reset_actions.ts`); it is intentionally excluded
74
+ * `actions` (see `testing/cross_backend/testing_reset_actions.ts`); it is intentionally excluded
75
75
  * here so it stays off the declared surface (the harness calls it directly
76
76
  * over the daemon-token channel).
77
77
  *
@@ -0,0 +1,14 @@
1
+ import '../assert_dev_env.js';
2
+ import { type CellCrossTestOptions } from './cell_cross_helpers.js';
3
+ import type { SetupTest } from './setup.js';
4
+ /**
5
+ * The fact suite adds one optional knob to the shared cell options: a setup
6
+ * variant whose keeper carries a **second actor**. Only the multi-actor case
7
+ * needs it; the rest of the suite runs single-actor, so wiring it is opt-in.
8
+ * Omit it and the multi-actor case silently skips.
9
+ */
10
+ export interface FactServingCrossTestOptions extends CellCrossTestOptions {
11
+ readonly setup_test_multi_actor?: SetupTest;
12
+ }
13
+ export declare const describe_fact_serving_cross_tests: (options: FactServingCrossTestOptions) => void;
14
+ //# sourceMappingURL=fact_serving.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,189 @@
1
+ import '../assert_dev_env.js';
2
+ /**
3
+ * Cross-backend fact-serving parity suite — the per-reference (cell-scoped)
4
+ * read model over real HTTP.
5
+ *
6
+ * Re-proves the D1 fact-access cases (`docs/security.md` § Fact Access Control)
7
+ * against each backend's real auth resolution, twinning fuz_app's
8
+ * `server/serve_fact_route.ts` and the Rust `fuz_fact_serving` routers:
9
+ *
10
+ * - **cell-scoped admit** — anon reads a fact through a viewable (public)
11
+ * referencing cell → 200 + bytes;
12
+ * - **cross-owner dedup does not leak** — A's *private* reference to bytes that
13
+ * B *also* publishes from a *public* cell stays 404 for everyone but A, even
14
+ * though the identical bytes are world-readable via B's cell (one deduped
15
+ * `fact` row; authz lives on the `(cell, hash)` edge, never unioned);
16
+ * - **404-mask** — a missing cell and a viewable cell that doesn't reference
17
+ * the hash both 404 (never 403, never "exists elsewhere");
18
+ * - **bare-hash admin-only** — `GET /api/facts/:hash` is admin (keeper) only:
19
+ * non-admin → 403, anonymous → 401;
20
+ * - **multi-actor fallthrough** — a multi-actor caller resolves to a null
21
+ * (anonymous) context on the (`acting`-less) cell-scoped route, so it can't
22
+ * read its own *private* fact there (admitted only by public cells). Opt-in
23
+ * (needs the multi-actor setup); every spine resolves the acting actor at the
24
+ * dispatcher's authorization phase from account-grain credentials, so the
25
+ * multi-actor account is drivable on TS and Rust alike.
26
+ *
27
+ * Facts are seeded **embedded** via `_testing_put_fact` (the cross-process
28
+ * driver has no DB handle); the referencing cell via the `cell_create` RPC
29
+ * (`extract_refs` lifts the `blake3:` hash in `data` into `cell.refs`). Gated
30
+ * on `capabilities.fact_serving`; runs under every `cross_backend_*` project —
31
+ * the TS spine binary and the Rust `testing_spine_stub` both mount the serve
32
+ * routes + the seeder.
33
+ *
34
+ * `$lib`-free by contract (relative specifiers only) so it imports from the
35
+ * spawnable cross-process test files.
36
+ *
37
+ * @module
38
+ */
39
+ import { describe, assert } from 'vitest';
40
+ import { z } from 'zod';
41
+ import { FactHashSchema } from '@fuzdev/fuz_util/fact_hash.js';
42
+ import { CellCreateOutput } from '../../auth/cell_action_specs.js';
43
+ import { test_if } from './capabilities.js';
44
+ import { cross_rpc_call, expect_output } from './cell_cross_helpers.js';
45
+ import { SPINE_RPC_PATH } from './default_spine_surface.js';
46
+ /** A blake3 hash that is a valid form but references no seeded fact. */
47
+ const UNRELATED_HASH = `blake3:${'0'.repeat(64)}`;
48
+ /** Nil UUID — a valid `Uuid` param that resolves to no cell. */
49
+ const NIL_UUID = '00000000-0000-0000-0000-000000000000';
50
+ /**
51
+ * `_testing_put_fact`'s output envelope. Parsing the seeder's `result` against
52
+ * it extends the cell suite's wire-shape parity gate (every RPC `result` is
53
+ * schema-checked) to the one fact-suite RPC that previously cast its output —
54
+ * a TS↔Rust drift in the seeder's envelope (extra field, non-`blake3:` hash)
55
+ * fails here rather than silently downstream.
56
+ */
57
+ const TestingPutFactOutput = z.strictObject({ hash: FactHashSchema });
58
+ /** GET a fact over a cross-process `FetchTransport`; returns `{status, content_type, text}`. */
59
+ const fact_get = async (transport, path, headers) => {
60
+ const res = await transport(path, { method: 'GET', headers });
61
+ return {
62
+ status: res.status,
63
+ content_type: res.headers.get('content-type'),
64
+ text: await res.text(),
65
+ };
66
+ };
67
+ export const describe_fact_serving_cross_tests = (options) => {
68
+ const { setup_test, setup_test_multi_actor, capabilities } = options;
69
+ const rpc_path = options.rpc_path ?? SPINE_RPC_PATH;
70
+ /** Seed an embedded fact over the daemon channel; returns its `blake3:` hash. */
71
+ const put_fact = async (fixture, content, content_type) => expect_output(await cross_rpc_call(fixture.transport, rpc_path, '_testing_put_fact', content_type === undefined ? { content } : { content, content_type }, fixture.create_daemon_token_headers()), TestingPutFactOutput).hash;
72
+ /**
73
+ * Create a cell referencing `hash` (`extract_refs` lifts it into `cell.refs`).
74
+ * Pass `acting` when the caller's account holds more than one actor —
75
+ * `cell_create` is `actor: 'required'`, so a multi-actor account must
76
+ * disambiguate or the authorization phase rejects with `actor_required`.
77
+ */
78
+ const create_cell_with_ref = async (transport, headers, hash, visibility, acting) => expect_output(await cross_rpc_call(transport, rpc_path, 'cell_create', acting === undefined
79
+ ? { data: { kind: 'doc', cover: hash }, visibility }
80
+ : { data: { kind: 'doc', cover: hash }, visibility, acting }, headers), CellCreateOutput).cell.id;
81
+ const cell_fact_path = (cell_id, hash) => `/api/cells/${cell_id}/facts/${hash}`;
82
+ describe('fact serving parity (cell-scoped per-reference reads)', () => {
83
+ test_if(capabilities.fact_serving, 'cell-scoped admit: anon reads a fact through a public referencing cell', async () => {
84
+ const fixture = await setup_test();
85
+ const hash = await put_fact(fixture, 'public-fact-bytes', 'text/plain');
86
+ const cell_id = await create_cell_with_ref(fixture.transport, fixture.create_session_headers(), hash, 'public');
87
+ const anon = fixture.fresh_transport({ origin: null });
88
+ const got = await fact_get(anon, cell_fact_path(cell_id, hash), {});
89
+ assert.strictEqual(got.status, 200, `expected 200, body: ${got.text}`);
90
+ assert.strictEqual(got.text, 'public-fact-bytes');
91
+ // The served `Content-Type` echoes the seeded value on both backends.
92
+ assert.strictEqual(got.content_type, 'text/plain', 'served content-type drifted');
93
+ });
94
+ test_if(capabilities.fact_serving, 'cross-owner dedup does not leak: A’s private reference stays 404 while B publishes the same bytes', async () => {
95
+ const fixture = await setup_test();
96
+ const a = await fixture.create_account({ username: 'fact_owner_a' });
97
+ const b = await fixture.create_account({ username: 'fact_owner_b' });
98
+ // One fact, identical bytes from both owners — deduped to a single row.
99
+ const hash = await put_fact(fixture, 'shared-deduped-bytes');
100
+ const t = fixture.fresh_transport();
101
+ const a_cell = await create_cell_with_ref(t, a.create_session_headers(), hash, 'private');
102
+ const b_cell = await create_cell_with_ref(t, b.create_session_headers(), hash, 'public');
103
+ // B published it from a public cell → readable via B's cell (anon OK).
104
+ const anon = fixture.fresh_transport({ origin: null });
105
+ const via_b = await fact_get(anon, cell_fact_path(b_cell, hash), {});
106
+ assert.strictEqual(via_b.status, 200, `B's public cell should serve the fact: ${via_b.text}`);
107
+ // Seeded without a content_type → both backends fall back to octet-stream.
108
+ assert.strictEqual(via_b.content_type, 'application/octet-stream', 'no-content-type fact should serve as octet-stream');
109
+ // A's PRIVATE reference must NOT leak — to anon or to B — even though
110
+ // the identical bytes are world-readable via B's public cell.
111
+ const a_via_anon = await fact_get(anon, cell_fact_path(a_cell, hash), {});
112
+ assert.strictEqual(a_via_anon.status, 404, "A's private reference leaked to anon");
113
+ const a_via_b = await fact_get(t, cell_fact_path(a_cell, hash), b.create_session_headers());
114
+ assert.strictEqual(a_via_b.status, 404, "A's private reference leaked to B");
115
+ // A can read its own private cell's fact.
116
+ const a_via_a = await fact_get(t, cell_fact_path(a_cell, hash), a.create_session_headers());
117
+ assert.strictEqual(a_via_a.status, 200, 'A could not read its own private fact');
118
+ });
119
+ test_if(capabilities.fact_serving, '404-mask: missing cell, missing edge, and edge-without-bytes all 404 alike', async () => {
120
+ const fixture = await setup_test();
121
+ const hash = await put_fact(fixture, '404-mask-bytes');
122
+ const t = fixture.fresh_transport();
123
+ const admin_headers = fixture.create_session_headers();
124
+ // Missing cell → 404.
125
+ const missing = await fact_get(t, cell_fact_path(NIL_UUID, hash), admin_headers);
126
+ assert.strictEqual(missing.status, 404, 'missing cell did not 404');
127
+ // A viewable (keeper-owned, public) cell that references a DIFFERENT
128
+ // hash → 404 (missing cell→fact edge), even though the caller can view
129
+ // the cell. `path` is admin-only and the keeper is admin.
130
+ const unrelated_cell = await create_cell_with_ref(t, admin_headers, UNRELATED_HASH, 'public');
131
+ const no_edge = await fact_get(t, cell_fact_path(unrelated_cell, hash), admin_headers);
132
+ assert.strictEqual(no_edge.status, 404, 'cell without the edge did not 404');
133
+ // Edge present but no `fact` row: the same cell DOES reference
134
+ // `UNRELATED_HASH`, which was never seeded. Cell + edge + view all
135
+ // pass, so this exercises the distinct serve-time "metadata missing"
136
+ // 404 branch — masked identically to the authz 404s above.
137
+ const edge_no_bytes = await fact_get(t, cell_fact_path(unrelated_cell, UNRELATED_HASH), admin_headers);
138
+ assert.strictEqual(edge_no_bytes.status, 404, 'edge to an absent fact did not 404');
139
+ });
140
+ test_if(capabilities.fact_serving, 'bare-hash route is admin-only', async () => {
141
+ const fixture = await setup_test();
142
+ const non_admin = await fixture.create_account({ username: 'fact_non_admin' });
143
+ const hash = await put_fact(fixture, 'bare-hash-bytes');
144
+ const t = fixture.fresh_transport();
145
+ // keeper holds ROLE_ADMIN → 200 + the actual bytes.
146
+ const as_admin = await fact_get(t, `/api/facts/${hash}`, fixture.create_session_headers());
147
+ assert.strictEqual(as_admin.status, 200, `admin bare-hash read failed: ${as_admin.text}`);
148
+ assert.strictEqual(as_admin.text, 'bare-hash-bytes', 'bare-hash route served wrong bytes');
149
+ // non-admin → 403.
150
+ const as_non_admin = await fact_get(t, `/api/facts/${hash}`, non_admin.create_session_headers());
151
+ assert.strictEqual(as_non_admin.status, 403, 'non-admin reached the bare-hash route');
152
+ // anonymous → 401.
153
+ const anon = fixture.fresh_transport({ origin: null });
154
+ const as_anon = await fact_get(anon, `/api/facts/${hash}`, {});
155
+ assert.strictEqual(as_anon.status, 401, 'anon reached the bare-hash route');
156
+ });
157
+ // The cell-scoped route is pure-public — no `acting?` slot — so the handler
158
+ // can't disambiguate a multi-actor account and resolves it to a null
159
+ // (anonymous) context. This pins that fallthrough: a multi-actor caller is
160
+ // admitted ONLY by public cells, never via owner / grant / admin — so it
161
+ // can't read its own PRIVATE fact through this route. (Contrast the
162
+ // single-actor owner in the cross-owner case above, who reads its own
163
+ // private fact → 200.) Gated on the opt-in multi-actor setup.
164
+ test_if(capabilities.fact_serving && setup_test_multi_actor !== undefined, 'multi-actor caller is treated as anonymous (public-only) on the cell-scoped route', async () => {
165
+ const fixture = await setup_test_multi_actor();
166
+ assert.ok(fixture.extra_actors.length >= 1, 'multi-actor setup seeded no second actor');
167
+ const t = fixture.fresh_transport();
168
+ const keeper = fixture.create_session_headers();
169
+ const acting = fixture.actor.id; // the keeper's bootstrap actor
170
+ // Seed a multi-actor account over the wire: the daemon-token put +
171
+ // the actor-required cell creates (disambiguated via `acting`) drive
172
+ // the keeper's two actors. Every spine resolves the acting actor at
173
+ // the dispatcher's authorization phase from account-grain credentials,
174
+ // so this runs on TS and Rust alike.
175
+ const hash = await put_fact(fixture, 'multi-actor-bytes');
176
+ const private_cell = await create_cell_with_ref(t, keeper, hash, 'private', acting);
177
+ const public_cell = await create_cell_with_ref(t, keeper, hash, 'public', acting);
178
+ // Owns a PRIVATE cell, yet reading its own fact back resolves to a null
179
+ // (anonymous) context → 404. The owner path never runs.
180
+ const own_private = await fact_get(t, cell_fact_path(private_cell, hash), keeper);
181
+ assert.strictEqual(own_private.status, 404, "multi-actor owner's private read was admitted");
182
+ // A PUBLIC cell still admits the anonymous-treated caller → 200, proving
183
+ // the 404 above is the multi-actor fallthrough, not a blanket block.
184
+ const via_public = await fact_get(t, cell_fact_path(public_cell, hash), keeper);
185
+ assert.strictEqual(via_public.status, 200, 'multi-actor caller blocked from a public cell');
186
+ assert.strictEqual(via_public.text, 'multi-actor-bytes');
187
+ });
188
+ });
189
+ };
@@ -24,7 +24,7 @@ export interface CreateTestAccountOptions {
24
24
  }
25
25
  /**
26
26
  * Shape returned by `TestFixture.create_account`. Aliased to the
27
- * existing `TestAccount` interface from `app_server.ts` — same shape,
27
+ * existing `TestAccount` interface from `testing/app_server.ts` — same shape,
28
28
  * stable name on the cross-backend testing surface so call sites read
29
29
  * `fixture.create_account(...)` returning `TestAccountFixture` without
30
30
  * crossing module boundaries.
@@ -225,7 +225,7 @@ export interface InProcessSetupOptions extends CreateTestAppOptions {
225
225
  *
226
226
  * The describe-level `auth_integration_truncate_tables` / pglite WASM
227
227
  * cache lifecycle stays in `create_pglite_factory` / `create_describe_db`
228
- * (`testing/db.js`) — `default_in_process_setup` doesn't manage db state
228
+ * (`testing/db.ts`) — `default_in_process_setup` doesn't manage db state
229
229
  * beyond what `create_test_app` already does.
230
230
  */
231
231
  export declare const default_in_process_setup: (options: InProcessSetupOptions) => SetupTest;
@@ -355,7 +355,7 @@ export interface CrossProcessSetupOptions {
355
355
  * Capture a backend's schema snapshot over the `_testing_schema_snapshot`
356
356
  * RPC action (keeper daemon-token channel). The canonical way for a
357
357
  * cross-impl parity gate to read each backend's live schema — pair two
358
- * calls with `assert_schema_snapshots_equal` (`testing/schema_parity.js`).
358
+ * calls with `assert_schema_snapshots_equal` (`testing/schema_parity.ts`).
359
359
  *
360
360
  * `exclude_tables` drops documented divergences from both sides before
361
361
  * comparison (e.g. a cell-primary Rust backend lacks tables the TS schema
@@ -82,7 +82,7 @@ const in_process_fetch_transport = (app) => {
82
82
  *
83
83
  * The describe-level `auth_integration_truncate_tables` / pglite WASM
84
84
  * cache lifecycle stays in `create_pglite_factory` / `create_describe_db`
85
- * (`testing/db.js`) — `default_in_process_setup` doesn't manage db state
85
+ * (`testing/db.ts`) — `default_in_process_setup` doesn't manage db state
86
86
  * beyond what `create_test_app` already does.
87
87
  */
88
88
  export const default_in_process_setup = (options) => async () => {
@@ -248,7 +248,7 @@ const rpc_via_transport = async (transport, rpc_path, method, params, backend_na
248
248
  * Capture a backend's schema snapshot over the `_testing_schema_snapshot`
249
249
  * RPC action (keeper daemon-token channel). The canonical way for a
250
250
  * cross-impl parity gate to read each backend's live schema — pair two
251
- * calls with `assert_schema_snapshots_equal` (`testing/schema_parity.js`).
251
+ * calls with `assert_schema_snapshots_equal` (`testing/schema_parity.ts`).
252
252
  *
253
253
  * `exclude_tables` drops documented divergences from both sides before
254
254
  * comparison (e.g. a cell-primary Rust backend lacks tables the TS schema
@@ -19,7 +19,7 @@ import '../assert_dev_env.js';
19
19
  * `_testing_reset` and other keeper-credential calls can authenticate.
20
20
  *
21
21
  * Bootstrapping (`POST /api/account/bootstrap`) is a separate concern —
22
- * the caller composes `bootstrap()` from `../transports/bootstrap.ts`
22
+ * the caller composes `bootstrap()` from `testing/transports/bootstrap.ts`
23
23
  * against a `FetchTransport` built around `handle.config.base_url`.
24
24
  * Splitting the two keeps `spawn_backend` consumer-agnostic — fuz_app
25
25
  * knows nothing about specific binary contents.
@@ -19,7 +19,7 @@ import '../assert_dev_env.js';
19
19
  * `_testing_reset` and other keeper-credential calls can authenticate.
20
20
  *
21
21
  * Bootstrapping (`POST /api/account/bootstrap`) is a separate concern —
22
- * the caller composes `bootstrap()` from `../transports/bootstrap.ts`
22
+ * the caller composes `bootstrap()` from `testing/transports/bootstrap.ts`
23
23
  * against a `FetchTransport` built around `handle.config.base_url`.
24
24
  * Splitting the two keeps `spawn_backend` consumer-agnostic — fuz_app
25
25
  * knows nothing about specific binary contents.
@@ -228,6 +228,40 @@ export declare const testing_mint_session_action_spec: {
228
228
  * `create_testing_actions`; in-process suites mount it directly).
229
229
  */
230
230
  export declare const create_testing_drain_effects_action: () => RpcAction;
231
+ /**
232
+ * `_testing_put_fact` — seed an **embedded** fact (`fact.bytes`) for the
233
+ * cross-process fact-serving suite, which drives over real HTTP and has no
234
+ * `PgFactStore` to call. Hashes the UTF-8 `content` (blake3, via
235
+ * `fact_hash_bytes` — the same hash the Rust `_testing_put_fact` computes),
236
+ * inserts the row idempotently, and returns `{hash}`. The referencing cell is
237
+ * seeded separately via the `cell_create` RPC. Embedded-only is enough for the
238
+ * authz assertions (cell-scoped admit, cross-owner-no-leak, 404-mask, bare-hash
239
+ * admin-only); external / X-Accel parity stays covered by the forge's own gate.
240
+ *
241
+ * `auth` gates on the daemon-token credential, matching `_testing_reset` — the
242
+ * action does a direct `fact` insert the production wire never exposes. The
243
+ * Rust mirror is `fuz_testing::create_testing_put_fact_action_spec`.
244
+ */
245
+ export declare const testing_put_fact_action_spec: {
246
+ readonly method: "_testing_put_fact";
247
+ readonly kind: "request_response";
248
+ readonly initiator: "frontend";
249
+ readonly auth: {
250
+ readonly account: "required";
251
+ readonly actor: "none";
252
+ readonly credential_types: readonly ["daemon_token"];
253
+ };
254
+ readonly side_effects: true;
255
+ readonly input: z.ZodObject<{
256
+ content: z.ZodString;
257
+ content_type: z.ZodOptional<z.ZodString>;
258
+ }, z.core.$strict>;
259
+ readonly output: z.ZodObject<{
260
+ hash: z.core.$ZodBranded<z.ZodString, "FactHash", "out">;
261
+ }, z.core.$strict>;
262
+ readonly async: true;
263
+ readonly description: string;
264
+ };
231
265
  /**
232
266
  * `_testing_schema_snapshot` — introspect the live database into a normalized
233
267
  * `SchemaSnapshot` for cross-impl parity diffing. The cross-backend harness
@@ -1 +1 @@
1
- {"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAGtB,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,6BAA6B,CAAC;AAEvE,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,gBAAgB,CAAC;AAmBvC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;QAiBpC;;;;;;;;WAQG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWyC,CAAC;AAE/C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;CAWA,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;CAeC,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,QAAO,SACiB,CAAC;AAEzE;;;;;;;;;GASG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWF,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,QAAO,SAGvD,CAAC;AAEH,4CAA4C;AAC5C,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAC9C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,OAAO,EACb,SAAS,2BAA2B,KAClC,KAAK,CAAC,SAAS,CAsHjB,CAAC;AAEF,0FAA0F;AAC1F,eAAO,MAAM,0BAA0B,UAAmC,CAAC"}
1
+ {"version":3,"file":"testing_reset_actions.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/testing_reset_actions.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAItB,OAAO,EAAa,KAAK,SAAS,EAAC,MAAM,6BAA6B,CAAC;AAGvE,OAAO,KAAK,EAAC,OAAO,EAAC,MAAM,oBAAoB,CAAC;AAChD,OAAO,KAAK,EAAC,cAAc,EAAC,MAAM,8BAA8B,CAAC;AACjE,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,4BAA4B,CAAC;AACjE,OAAO,KAAK,EAAC,EAAE,EAAC,MAAM,gBAAgB,CAAC;AAmBvC;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;QAiBpC;;;;;;;;WAQG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWyC,CAAC;AAE/C;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,iCAAiC;;;;;;;;;;;;;;;;CAWA,CAAC;AAE/C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,eAAO,MAAM,gCAAgC;;;;;;;;;;;;;;;;;;;CAeC,CAAC;AAE/C;;;;;;GAMG;AACH,eAAO,MAAM,mCAAmC,QAAO,SACiB,CAAC;AAEzE;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,4BAA4B;;;;;;;;;;;;;;;;;;;CAeK,CAAC;AAE/C;;;;;;;;;GASG;AACH,eAAO,MAAM,mCAAmC;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAWF,CAAC;AAE/C;;;;GAIG;AACH,eAAO,MAAM,qCAAqC,QAAO,SAGvD,CAAC;AAEH,4CAA4C;AAC5C,MAAM,WAAW,2BAA2B;IAC3C;;;;;OAKG;IACH,QAAQ,CAAC,eAAe,EAAE,cAAc,CAAC,MAAM,CAAC,CAAC;IACjD;;;;;OAKG;IACH,QAAQ,CAAC,kBAAkB,EAAE,gBAAgB,CAAC;IAC9C;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;CACxD;AAED;;;;;;;;;;;;;GAaG;AACH,eAAO,MAAM,sBAAsB,GAClC,MAAM,OAAO,EACb,SAAS,2BAA2B,KAClC,KAAK,CAAC,SAAS,CAqIjB,CAAC;AAEF,0FAA0F;AAC1F,eAAO,MAAM,0BAA0B,UAAmC,CAAC"}
@@ -60,7 +60,9 @@ import '../assert_dev_env.js';
60
60
  */
61
61
  import { z } from 'zod';
62
62
  import { Uuid } from '@fuzdev/fuz_util/id.js';
63
+ import { fact_hash_bytes, FactHashSchema } from '@fuzdev/fuz_util/fact_hash.js';
63
64
  import { rpc_action } from '../../actions/action_rpc.js';
65
+ import { query_put_fact } from '../../db/fact_queries.js';
64
66
  import { ROLE_ADMIN, ROLE_KEEPER } from '../../auth/role_schema.js';
65
67
  import { auth_integration_truncate_tables } from '../db.js';
66
68
  import { query_schema_snapshot, SchemaSnapshot } from '../schema_introspect.js';
@@ -203,6 +205,35 @@ export const testing_mint_session_action_spec = {
203
205
  * `create_testing_actions`; in-process suites mount it directly).
204
206
  */
205
207
  export const create_testing_drain_effects_action = () => rpc_action(testing_drain_effects_action_spec, async () => ({ ok: true }));
208
+ /**
209
+ * `_testing_put_fact` — seed an **embedded** fact (`fact.bytes`) for the
210
+ * cross-process fact-serving suite, which drives over real HTTP and has no
211
+ * `PgFactStore` to call. Hashes the UTF-8 `content` (blake3, via
212
+ * `fact_hash_bytes` — the same hash the Rust `_testing_put_fact` computes),
213
+ * inserts the row idempotently, and returns `{hash}`. The referencing cell is
214
+ * seeded separately via the `cell_create` RPC. Embedded-only is enough for the
215
+ * authz assertions (cell-scoped admit, cross-owner-no-leak, 404-mask, bare-hash
216
+ * admin-only); external / X-Accel parity stays covered by the forge's own gate.
217
+ *
218
+ * `auth` gates on the daemon-token credential, matching `_testing_reset` — the
219
+ * action does a direct `fact` insert the production wire never exposes. The
220
+ * Rust mirror is `fuz_testing::create_testing_put_fact_action_spec`.
221
+ */
222
+ export const testing_put_fact_action_spec = {
223
+ method: '_testing_put_fact',
224
+ kind: 'request_response',
225
+ initiator: 'frontend',
226
+ auth: { account: 'required', actor: 'none', credential_types: ['daemon_token'] },
227
+ side_effects: true,
228
+ input: z.strictObject({
229
+ content: z.string(),
230
+ content_type: z.string().optional(),
231
+ }),
232
+ output: z.strictObject({ hash: FactHashSchema }),
233
+ async: true,
234
+ description: 'Test-binary only — seed an embedded fact (blake3 of the UTF-8 content) and return its hash, ' +
235
+ 'so the cross-process fact-serving suite can store bytes without a DB handle.',
236
+ };
206
237
  /**
207
238
  * `_testing_schema_snapshot` — introspect the live database into a normalized
208
239
  * `SchemaSnapshot` for cross-impl parity diffing. The cross-backend harness
@@ -349,6 +380,18 @@ export const create_testing_actions = (deps, options) => {
349
380
  });
350
381
  return { session_cookie };
351
382
  }),
383
+ rpc_action(testing_put_fact_action_spec, async (input, ctx) => {
384
+ const bytes = new TextEncoder().encode(input.content);
385
+ const hash = fact_hash_bytes(bytes);
386
+ await query_put_fact({ db: ctx.db }, {
387
+ hash,
388
+ bytes,
389
+ external_url: null,
390
+ content_type: input.content_type ?? null,
391
+ size: bytes.length,
392
+ });
393
+ return { hash };
394
+ }),
352
395
  create_testing_drain_effects_action(),
353
396
  create_testing_schema_snapshot_action(),
354
397
  ];
@@ -3,8 +3,8 @@ import '../assert_dev_env.js';
3
3
  * Bun runtime adapter for spawnable cross-process test server binaries.
4
4
  *
5
5
  * Binds `Bun.serve` and `hono/bun`'s module-level `upgradeWebSocket` +
6
- * `websocket` handler. The shared `testing_server_core.ts` owns the rest.
7
- * Third sibling to `testing_server_node.ts` / `testing_server_deno.ts` —
6
+ * `websocket` handler. The shared `testing/cross_backend/testing_server_core.ts` owns the rest.
7
+ * Third sibling to `testing/cross_backend/testing_server_node.ts` / `testing/cross_backend/testing_server_deno.ts` —
8
8
  * together the three isolate the JS-runtime axis (Node V8 / Deno V8 / Bun
9
9
  * JSC) on identical TS surfaces, and the Rust spine binary covers the
10
10
  * cross-language axis.
@@ -15,7 +15,7 @@ import '../assert_dev_env.js';
15
15
  * implements the `node:fs` / `node:process` surface `RuntimeDeps` +
16
16
  * `cli/daemon` touch.
17
17
  *
18
- * `Bun.serve` is declared locally (mirroring `testing_server_deno.ts`'s
18
+ * `Bun.serve` is declared locally (mirroring `testing/cross_backend/testing_server_deno.ts`'s
19
19
  * `Deno` declaration) so this module typechecks under fuz_app's Node-based
20
20
  * config without `@types/bun`. It is only ever *run* under Bun.
21
21
  *