@fuzdev/fuz_app 0.67.1 → 0.69.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.
- package/dist/actions/perform_action.d.ts.map +1 -1
- package/dist/actions/perform_action.js +10 -3
- package/dist/auth/CLAUDE.md +99 -5
- package/dist/auth/account_queries.d.ts +87 -4
- package/dist/auth/account_queries.d.ts.map +1 -1
- package/dist/auth/account_queries.js +107 -17
- package/dist/auth/account_schema.d.ts +19 -0
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/account_schema.js +8 -0
- package/dist/auth/admin_action_specs.d.ts +170 -3
- package/dist/auth/admin_action_specs.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.js +148 -4
- package/dist/auth/admin_actions.d.ts +4 -14
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +246 -40
- package/dist/auth/audit_log_ddl.d.ts +10 -1
- package/dist/auth/audit_log_ddl.d.ts.map +1 -1
- package/dist/auth/audit_log_ddl.js +13 -4
- package/dist/auth/audit_log_schema.d.ts +34 -1
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +73 -0
- package/dist/auth/auth_ddl.d.ts +2 -2
- package/dist/auth/auth_ddl.d.ts.map +1 -1
- package/dist/auth/auth_ddl.js +10 -2
- package/dist/auth/cell_action_specs.d.ts +1295 -0
- package/dist/auth/cell_action_specs.d.ts.map +1 -0
- package/dist/auth/cell_action_specs.js +397 -0
- package/dist/auth/cell_actions.d.ts +63 -0
- package/dist/auth/cell_actions.d.ts.map +1 -0
- package/dist/auth/cell_actions.js +546 -0
- package/dist/auth/cell_audit_action_specs.d.ts +131 -0
- package/dist/auth/cell_audit_action_specs.d.ts.map +1 -0
- package/dist/auth/cell_audit_action_specs.js +70 -0
- package/dist/auth/cell_audit_actions.d.ts +18 -0
- package/dist/auth/cell_audit_actions.d.ts.map +1 -0
- package/dist/auth/cell_audit_actions.js +59 -0
- package/dist/auth/cell_audit_events.d.ts +28 -0
- package/dist/auth/cell_audit_events.d.ts.map +1 -0
- package/dist/auth/cell_audit_events.js +42 -0
- package/dist/auth/cell_audit_metadata.d.ts +48 -0
- package/dist/auth/cell_audit_metadata.d.ts.map +1 -0
- package/dist/auth/cell_audit_metadata.js +46 -0
- package/dist/auth/cell_authorize.d.ts +88 -0
- package/dist/auth/cell_authorize.d.ts.map +1 -0
- package/dist/auth/cell_authorize.js +172 -0
- package/dist/auth/cell_data_schema.d.ts +44 -0
- package/dist/auth/cell_data_schema.d.ts.map +1 -0
- package/dist/auth/cell_data_schema.js +42 -0
- package/dist/auth/cell_field_action_specs.d.ts +244 -0
- package/dist/auth/cell_field_action_specs.d.ts.map +1 -0
- package/dist/auth/cell_field_action_specs.js +136 -0
- package/dist/auth/cell_field_actions.d.ts +34 -0
- package/dist/auth/cell_field_actions.d.ts.map +1 -0
- package/dist/auth/cell_field_actions.js +153 -0
- package/dist/auth/cell_field_audit_metadata.d.ts +30 -0
- package/dist/auth/cell_field_audit_metadata.d.ts.map +1 -0
- package/dist/auth/cell_field_audit_metadata.js +28 -0
- package/dist/auth/cell_grant_action_specs.d.ts +333 -0
- package/dist/auth/cell_grant_action_specs.d.ts.map +1 -0
- package/dist/auth/cell_grant_action_specs.js +148 -0
- package/dist/auth/cell_grant_actions.d.ts +50 -0
- package/dist/auth/cell_grant_actions.d.ts.map +1 -0
- package/dist/auth/cell_grant_actions.js +208 -0
- package/dist/auth/cell_grant_audit_metadata.d.ts +75 -0
- package/dist/auth/cell_grant_audit_metadata.d.ts.map +1 -0
- package/dist/auth/cell_grant_audit_metadata.js +54 -0
- package/dist/auth/cell_item_action_specs.d.ts +331 -0
- package/dist/auth/cell_item_action_specs.d.ts.map +1 -0
- package/dist/auth/cell_item_action_specs.js +182 -0
- package/dist/auth/cell_item_actions.d.ts +37 -0
- package/dist/auth/cell_item_actions.d.ts.map +1 -0
- package/dist/auth/cell_item_actions.js +204 -0
- package/dist/auth/cell_item_audit_metadata.d.ts +35 -0
- package/dist/auth/cell_item_audit_metadata.d.ts.map +1 -0
- package/dist/auth/cell_item_audit_metadata.js +32 -0
- package/dist/auth/cell_relation_visibility.d.ts +32 -0
- package/dist/auth/cell_relation_visibility.d.ts.map +1 -0
- package/dist/auth/cell_relation_visibility.js +57 -0
- package/dist/auth/deps.d.ts +9 -0
- package/dist/auth/deps.d.ts.map +1 -1
- package/dist/auth/role_grant_queries.d.ts +30 -0
- package/dist/auth/role_grant_queries.d.ts.map +1 -1
- package/dist/auth/role_grant_queries.js +54 -0
- package/dist/auth/signup_routes.d.ts +0 -3
- package/dist/auth/signup_routes.d.ts.map +1 -1
- package/dist/auth/signup_routes.js +9 -3
- package/dist/auth/standard_rpc_actions.d.ts +5 -5
- package/dist/auth/standard_rpc_actions.js +4 -4
- package/dist/db/CLAUDE.md +118 -0
- package/dist/db/cell_audit_queries.d.ts +26 -0
- package/dist/db/cell_audit_queries.d.ts.map +1 -0
- package/dist/db/cell_audit_queries.js +53 -0
- package/dist/db/cell_ddl.d.ts +151 -0
- package/dist/db/cell_ddl.d.ts.map +1 -0
- package/dist/db/cell_ddl.js +247 -0
- package/dist/db/cell_field_queries.d.ts +105 -0
- package/dist/db/cell_field_queries.d.ts.map +1 -0
- package/dist/db/cell_field_queries.js +113 -0
- package/dist/db/cell_grant_queries.d.ts +132 -0
- package/dist/db/cell_grant_queries.d.ts.map +1 -0
- package/dist/db/cell_grant_queries.js +145 -0
- package/dist/db/cell_history_ddl.d.ts +38 -0
- package/dist/db/cell_history_ddl.d.ts.map +1 -0
- package/dist/db/cell_history_ddl.js +61 -0
- package/dist/db/cell_item_queries.d.ts +107 -0
- package/dist/db/cell_item_queries.d.ts.map +1 -0
- package/dist/db/cell_item_queries.js +119 -0
- package/dist/db/cell_queries.d.ts +327 -0
- package/dist/db/cell_queries.d.ts.map +1 -0
- package/dist/db/cell_queries.js +431 -0
- package/dist/db/fact_ddl.d.ts +38 -0
- package/dist/db/fact_ddl.d.ts.map +1 -0
- package/dist/db/fact_ddl.js +71 -0
- package/dist/db/fact_queries.d.ts +140 -0
- package/dist/db/fact_queries.d.ts.map +1 -0
- package/dist/db/fact_queries.js +161 -0
- package/dist/db/fact_store.d.ts +112 -0
- package/dist/db/fact_store.d.ts.map +1 -0
- package/dist/db/fact_store.js +225 -0
- package/dist/server/app_server.d.ts +1 -7
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +1 -5
- package/dist/server/env.d.ts +2 -0
- package/dist/server/env.d.ts.map +1 -1
- package/dist/server/env.js +6 -0
- package/dist/server/fact_write.d.ts +32 -0
- package/dist/server/fact_write.d.ts.map +1 -0
- package/dist/server/fact_write.js +56 -0
- package/dist/server/file_fact_fetcher.d.ts +42 -0
- package/dist/server/file_fact_fetcher.d.ts.map +1 -0
- package/dist/server/file_fact_fetcher.js +60 -0
- package/dist/server/file_fact_url.d.ts +53 -0
- package/dist/server/file_fact_url.d.ts.map +1 -0
- package/dist/server/file_fact_url.js +52 -0
- package/dist/server/serve_fact_route.d.ts +78 -0
- package/dist/server/serve_fact_route.d.ts.map +1 -0
- package/dist/server/serve_fact_route.js +205 -0
- package/dist/testing/CLAUDE.md +142 -6
- package/dist/testing/app_server.d.ts +46 -0
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +67 -8
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +67 -1
- package/dist/testing/cross_backend/account_lifecycle.d.ts +10 -0
- package/dist/testing/cross_backend/account_lifecycle.d.ts.map +1 -0
- package/dist/testing/cross_backend/account_lifecycle.js +144 -0
- package/dist/testing/cross_backend/actor_lookup.d.ts +10 -0
- package/dist/testing/cross_backend/actor_lookup.d.ts.map +1 -0
- package/dist/testing/cross_backend/actor_lookup.js +83 -0
- package/dist/testing/cross_backend/actor_search.d.ts +6 -0
- package/dist/testing/cross_backend/actor_search.d.ts.map +1 -0
- package/dist/testing/cross_backend/actor_search.js +92 -0
- package/dist/testing/cross_backend/app_settings.d.ts +6 -0
- package/dist/testing/cross_backend/app_settings.d.ts.map +1 -0
- package/dist/testing/cross_backend/app_settings.js +95 -0
- package/dist/testing/cross_backend/backend_config.d.ts +1 -1
- package/dist/testing/cross_backend/capabilities.d.ts +29 -7
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -1
- package/dist/testing/cross_backend/capabilities.js +3 -1
- package/dist/testing/cross_backend/cell_cross_helpers.d.ts +39 -0
- package/dist/testing/cross_backend/cell_cross_helpers.d.ts.map +1 -0
- package/dist/testing/cross_backend/cell_cross_helpers.js +45 -0
- package/dist/testing/cross_backend/cell_crud.d.ts +4 -0
- package/dist/testing/cross_backend/cell_crud.d.ts.map +1 -0
- package/dist/testing/cross_backend/cell_crud.js +168 -0
- package/dist/testing/cross_backend/cell_grant_role.d.ts +8 -0
- package/dist/testing/cross_backend/cell_grant_role.d.ts.map +1 -0
- package/dist/testing/cross_backend/cell_grant_role.js +102 -0
- package/dist/testing/cross_backend/cell_relations.d.ts +4 -0
- package/dist/testing/cross_backend/cell_relations.d.ts.map +1 -0
- package/dist/testing/cross_backend/cell_relations.js +229 -0
- package/dist/testing/cross_backend/conformance_case.d.ts +144 -0
- package/dist/testing/cross_backend/conformance_case.d.ts.map +1 -0
- package/dist/testing/cross_backend/conformance_case.js +132 -0
- package/dist/testing/cross_backend/conformance_table.d.ts +46 -0
- package/dist/testing/cross_backend/conformance_table.d.ts.map +1 -0
- package/dist/testing/cross_backend/conformance_table.js +199 -0
- package/dist/testing/cross_backend/default_backend_configs.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_backend_configs.js +6 -2
- package/dist/testing/cross_backend/default_spine_surface.d.ts +17 -9
- package/dist/testing/cross_backend/default_spine_surface.d.ts.map +1 -1
- package/dist/testing/cross_backend/default_spine_surface.js +20 -12
- package/dist/testing/cross_backend/origin.d.ts +10 -0
- package/dist/testing/cross_backend/origin.d.ts.map +1 -0
- package/dist/testing/cross_backend/origin.js +73 -0
- package/dist/testing/cross_backend/setup.d.ts +22 -40
- package/dist/testing/cross_backend/setup.d.ts.map +1 -1
- package/dist/testing/cross_backend/setup.js +39 -5
- package/dist/testing/cross_backend/testing_reset_actions.d.ts +90 -2
- package/dist/testing/cross_backend/testing_reset_actions.d.ts.map +1 -1
- package/dist/testing/cross_backend/testing_reset_actions.js +91 -3
- package/dist/testing/cross_backend/xfail.d.ts +15 -0
- package/dist/testing/cross_backend/xfail.d.ts.map +1 -0
- package/dist/testing/cross_backend/xfail.js +37 -0
- package/dist/testing/entities.d.ts.map +1 -1
- package/dist/testing/entities.js +4 -0
- package/dist/testing/integration.d.ts +2 -3
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +20 -85
- package/dist/testing/rate_limiting.d.ts +1 -1
- package/dist/testing/rpc_helpers.d.ts +3 -3
- package/dist/testing/sse_round_trip.d.ts +1 -1
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +0 -1
- package/dist/testing/ws_round_trip.d.ts.map +1 -1
- package/dist/testing/ws_round_trip.js +4 -0
- package/dist/ui/AdminAccounts.svelte +84 -35
- package/dist/ui/AdminAccounts.svelte.d.ts.map +1 -1
- package/dist/ui/AdminSessions.svelte +21 -23
- package/dist/ui/AdminSessions.svelte.d.ts.map +1 -1
- package/dist/ui/CLAUDE.md +17 -26
- package/dist/ui/OpenSignupToggle.svelte +2 -5
- package/dist/ui/OpenSignupToggle.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.d.ts +9 -10
- package/dist/ui/account_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/account_sessions_state.svelte.js +7 -17
- package/dist/ui/admin_accounts_state.svelte.d.ts +41 -20
- package/dist/ui/admin_accounts_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_accounts_state.svelte.js +52 -22
- package/dist/ui/admin_invites_state.svelte.d.ts +8 -11
- package/dist/ui/admin_invites_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_invites_state.svelte.js +7 -16
- package/dist/ui/admin_rpc_adapters.d.ts +6 -2
- package/dist/ui/admin_rpc_adapters.d.ts.map +1 -1
- package/dist/ui/admin_rpc_adapters.js +5 -1
- package/dist/ui/admin_sessions_state.svelte.d.ts +6 -10
- package/dist/ui/admin_sessions_state.svelte.d.ts.map +1 -1
- package/dist/ui/admin_sessions_state.svelte.js +4 -14
- package/dist/ui/app_settings_state.svelte.d.ts +8 -12
- package/dist/ui/app_settings_state.svelte.d.ts.map +1 -1
- package/dist/ui/app_settings_state.svelte.js +6 -16
- package/dist/ui/audit_log_state.svelte.d.ts +9 -8
- package/dist/ui/audit_log_state.svelte.d.ts.map +1 -1
- package/dist/ui/audit_log_state.svelte.js +8 -20
- package/package.json +2 -2
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Declarative conformance-case schema for the cross-backend behavioral +
|
|
4
|
+
* security suite.
|
|
5
|
+
*
|
|
6
|
+
* A conformance case is a single request → expected-response assertion,
|
|
7
|
+
* carried as **data**. The case references a `method` (an RPC method name
|
|
8
|
+
* or a REST auth-route suffix); the runner
|
|
9
|
+
* (`describe_conformance_table_tests`) resolves the `input` / `output`
|
|
10
|
+
* Zod schemas from the live action-spec registry / `RouteSpec` — the case
|
|
11
|
+
* never carries a schema. This is the opinionated behavioral/security
|
|
12
|
+
* layer on top of the spec-derived auto-enumeration
|
|
13
|
+
* (`describe_rpc_round_trip_tests` / `describe_rpc_attack_surface_tests`):
|
|
14
|
+
* the same case definition runs in-process (fast, every `gro test`) and
|
|
15
|
+
* cross-process (the conformance gate) against each impl's real auth
|
|
16
|
+
* resolution.
|
|
17
|
+
*
|
|
18
|
+
* The table is for single-request matrices (credential-type ceiling,
|
|
19
|
+
* privilege gates, IDOR masks, enumeration-equivalence, validation).
|
|
20
|
+
* Multi-step flows stay imperative in their own `describe_*` suites,
|
|
21
|
+
* sharing assertion primitives — there is deliberately no declarative
|
|
22
|
+
* setup DSL.
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
import { z } from 'zod';
|
|
27
|
+
/**
|
|
28
|
+
* Closed enum of fixture-provisioned principals a case runs `as`. Each
|
|
29
|
+
* value maps to a `TestFixture` accessor (or a seeded `extra_accounts`
|
|
30
|
+
* entry) in the runner's `resolve_principal` — there is **no** inline
|
|
31
|
+
* credential minting in a case (that would be the setup-DSL trap).
|
|
32
|
+
*
|
|
33
|
+
* - `keeper` — the per-test bootstrapped keeper (holds `ROLE_KEEPER` +
|
|
34
|
+
* `ROLE_ADMIN`), session credential.
|
|
35
|
+
* - `daemon` — the keeper authenticated via the daemon-token header.
|
|
36
|
+
* - `token` — the keeper authenticated via a bearer api-token (non-browser
|
|
37
|
+
* context; the runner suppresses `Origin` so the token isn't discarded).
|
|
38
|
+
* - `anonymous` — no credential, fresh cookie jar.
|
|
39
|
+
* - `fresh_non_admin` — a freshly minted account with no roles, session
|
|
40
|
+
* credential (via the production invite → signup → login flow).
|
|
41
|
+
* - `role_holder` — a seeded `extra_accounts` principal holding a specific
|
|
42
|
+
* role; the runner reads it by the username named in
|
|
43
|
+
* `ConformanceTableOptions.principals.role_holder`.
|
|
44
|
+
* - `wrong_role` — a seeded `extra_accounts` principal holding a role
|
|
45
|
+
* other than the one a route requires; named via
|
|
46
|
+
* `ConformanceTableOptions.principals.wrong_role`.
|
|
47
|
+
* - `expired_session` — the keeper account presented via an *expired
|
|
48
|
+
* server-side session* cookie (minted by `fixture.mint_expired_session()`:
|
|
49
|
+
* a backdated `auth_session` row behind a still-valid signed cookie
|
|
50
|
+
* payload, so the authoritative DB-row expiry gate is what refuses it).
|
|
51
|
+
*/
|
|
52
|
+
export declare const ConformancePrincipal: z.ZodEnum<{
|
|
53
|
+
keeper: "keeper";
|
|
54
|
+
token: "token";
|
|
55
|
+
daemon: "daemon";
|
|
56
|
+
anonymous: "anonymous";
|
|
57
|
+
fresh_non_admin: "fresh_non_admin";
|
|
58
|
+
role_holder: "role_holder";
|
|
59
|
+
wrong_role: "wrong_role";
|
|
60
|
+
expired_session: "expired_session";
|
|
61
|
+
}>;
|
|
62
|
+
export type ConformancePrincipal = z.infer<typeof ConformancePrincipal>;
|
|
63
|
+
/** The request a conformance case issues. */
|
|
64
|
+
export declare const ConformanceCaseRequest: z.ZodObject<{
|
|
65
|
+
method: z.ZodString;
|
|
66
|
+
params: z.ZodOptional<z.ZodUnknown>;
|
|
67
|
+
as: z.ZodEnum<{
|
|
68
|
+
keeper: "keeper";
|
|
69
|
+
token: "token";
|
|
70
|
+
daemon: "daemon";
|
|
71
|
+
anonymous: "anonymous";
|
|
72
|
+
fresh_non_admin: "fresh_non_admin";
|
|
73
|
+
role_holder: "role_holder";
|
|
74
|
+
wrong_role: "wrong_role";
|
|
75
|
+
expired_session: "expired_session";
|
|
76
|
+
}>;
|
|
77
|
+
verb: z.ZodOptional<z.ZodEnum<{
|
|
78
|
+
GET: "GET";
|
|
79
|
+
POST: "POST";
|
|
80
|
+
}>>;
|
|
81
|
+
}, z.core.$strict>;
|
|
82
|
+
export type ConformanceCaseRequest = z.infer<typeof ConformanceCaseRequest>;
|
|
83
|
+
/** The expected response shape a conformance case asserts. */
|
|
84
|
+
export declare const ConformanceCaseExpectation: z.ZodObject<{
|
|
85
|
+
status: z.ZodNumber;
|
|
86
|
+
error_reason: z.ZodOptional<z.ZodString>;
|
|
87
|
+
fields: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
88
|
+
}, z.core.$strict>;
|
|
89
|
+
export type ConformanceCaseExpectation = z.infer<typeof ConformanceCaseExpectation>;
|
|
90
|
+
/**
|
|
91
|
+
* Marks a case as a deferred-by-design gap. The runner routes it through
|
|
92
|
+
* `xfail_until` instead of a normal `test` — visible (distinct from pass)
|
|
93
|
+
* and self-cleaning (flips red when the impl starts passing, forcing the
|
|
94
|
+
* marker's removal). Use for declared gaps (e.g. facts), never for
|
|
95
|
+
* in-scope gaps (those fail loud as a red `test`).
|
|
96
|
+
*/
|
|
97
|
+
export declare const ConformanceCaseXfail: z.ZodObject<{
|
|
98
|
+
tracking_id: z.ZodString;
|
|
99
|
+
reason: z.ZodString;
|
|
100
|
+
}, z.core.$strict>;
|
|
101
|
+
export type ConformanceCaseXfail = z.infer<typeof ConformanceCaseXfail>;
|
|
102
|
+
/**
|
|
103
|
+
* A single conformance case. `name` is the assertion; the optional
|
|
104
|
+
* free-text `note` is printed in the test label / failure output. A
|
|
105
|
+
* security case's `note` should reference a **public** fuz_app doc
|
|
106
|
+
* property (`security.md` / `architecture.md` / module TSDoc), since the
|
|
107
|
+
* table ships in a public package — not an internal planning doc. The note
|
|
108
|
+
* is documentation, not a gate: it stays free-text by design because a
|
|
109
|
+
* non-empty-string check never catches a *wrong* citation — the citation
|
|
110
|
+
* is verified in review.
|
|
111
|
+
*/
|
|
112
|
+
export declare const ConformanceCase: z.ZodObject<{
|
|
113
|
+
name: z.ZodString;
|
|
114
|
+
request: z.ZodObject<{
|
|
115
|
+
method: z.ZodString;
|
|
116
|
+
params: z.ZodOptional<z.ZodUnknown>;
|
|
117
|
+
as: z.ZodEnum<{
|
|
118
|
+
keeper: "keeper";
|
|
119
|
+
token: "token";
|
|
120
|
+
daemon: "daemon";
|
|
121
|
+
anonymous: "anonymous";
|
|
122
|
+
fresh_non_admin: "fresh_non_admin";
|
|
123
|
+
role_holder: "role_holder";
|
|
124
|
+
wrong_role: "wrong_role";
|
|
125
|
+
expired_session: "expired_session";
|
|
126
|
+
}>;
|
|
127
|
+
verb: z.ZodOptional<z.ZodEnum<{
|
|
128
|
+
GET: "GET";
|
|
129
|
+
POST: "POST";
|
|
130
|
+
}>>;
|
|
131
|
+
}, z.core.$strict>;
|
|
132
|
+
expect: z.ZodObject<{
|
|
133
|
+
status: z.ZodNumber;
|
|
134
|
+
error_reason: z.ZodOptional<z.ZodString>;
|
|
135
|
+
fields: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
136
|
+
}, z.core.$strict>;
|
|
137
|
+
note: z.ZodOptional<z.ZodString>;
|
|
138
|
+
xfail: z.ZodOptional<z.ZodObject<{
|
|
139
|
+
tracking_id: z.ZodString;
|
|
140
|
+
reason: z.ZodString;
|
|
141
|
+
}, z.core.$strict>>;
|
|
142
|
+
}, z.core.$strict>;
|
|
143
|
+
export type ConformanceCase = z.infer<typeof ConformanceCase>;
|
|
144
|
+
//# sourceMappingURL=conformance_case.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conformance_case.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/conformance_case.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAC,CAAC,EAAC,MAAM,KAAK,CAAC;AAEtB;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;EAS/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE,6CAA6C;AAC7C,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;kBAgBjC,CAAC;AACH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,sBAAsB,CAAC,CAAC;AAE5E,8DAA8D;AAC9D,eAAO,MAAM,0BAA0B;;;;kBAqBrC,CAAC;AACH,MAAM,MAAM,0BAA0B,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,0BAA0B,CAAC,CAAC;AAEpF;;;;;;GAMG;AACH,eAAO,MAAM,oBAAoB;;;kBAK/B,CAAC;AACH,MAAM,MAAM,oBAAoB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAExE;;;;;;;;;GASG;AACH,eAAO,MAAM,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;kBAS1B,CAAC;AACH,MAAM,MAAM,eAAe,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,eAAe,CAAC,CAAC"}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Declarative conformance-case schema for the cross-backend behavioral +
|
|
4
|
+
* security suite.
|
|
5
|
+
*
|
|
6
|
+
* A conformance case is a single request → expected-response assertion,
|
|
7
|
+
* carried as **data**. The case references a `method` (an RPC method name
|
|
8
|
+
* or a REST auth-route suffix); the runner
|
|
9
|
+
* (`describe_conformance_table_tests`) resolves the `input` / `output`
|
|
10
|
+
* Zod schemas from the live action-spec registry / `RouteSpec` — the case
|
|
11
|
+
* never carries a schema. This is the opinionated behavioral/security
|
|
12
|
+
* layer on top of the spec-derived auto-enumeration
|
|
13
|
+
* (`describe_rpc_round_trip_tests` / `describe_rpc_attack_surface_tests`):
|
|
14
|
+
* the same case definition runs in-process (fast, every `gro test`) and
|
|
15
|
+
* cross-process (the conformance gate) against each impl's real auth
|
|
16
|
+
* resolution.
|
|
17
|
+
*
|
|
18
|
+
* The table is for single-request matrices (credential-type ceiling,
|
|
19
|
+
* privilege gates, IDOR masks, enumeration-equivalence, validation).
|
|
20
|
+
* Multi-step flows stay imperative in their own `describe_*` suites,
|
|
21
|
+
* sharing assertion primitives — there is deliberately no declarative
|
|
22
|
+
* setup DSL.
|
|
23
|
+
*
|
|
24
|
+
* @module
|
|
25
|
+
*/
|
|
26
|
+
import { z } from 'zod';
|
|
27
|
+
/**
|
|
28
|
+
* Closed enum of fixture-provisioned principals a case runs `as`. Each
|
|
29
|
+
* value maps to a `TestFixture` accessor (or a seeded `extra_accounts`
|
|
30
|
+
* entry) in the runner's `resolve_principal` — there is **no** inline
|
|
31
|
+
* credential minting in a case (that would be the setup-DSL trap).
|
|
32
|
+
*
|
|
33
|
+
* - `keeper` — the per-test bootstrapped keeper (holds `ROLE_KEEPER` +
|
|
34
|
+
* `ROLE_ADMIN`), session credential.
|
|
35
|
+
* - `daemon` — the keeper authenticated via the daemon-token header.
|
|
36
|
+
* - `token` — the keeper authenticated via a bearer api-token (non-browser
|
|
37
|
+
* context; the runner suppresses `Origin` so the token isn't discarded).
|
|
38
|
+
* - `anonymous` — no credential, fresh cookie jar.
|
|
39
|
+
* - `fresh_non_admin` — a freshly minted account with no roles, session
|
|
40
|
+
* credential (via the production invite → signup → login flow).
|
|
41
|
+
* - `role_holder` — a seeded `extra_accounts` principal holding a specific
|
|
42
|
+
* role; the runner reads it by the username named in
|
|
43
|
+
* `ConformanceTableOptions.principals.role_holder`.
|
|
44
|
+
* - `wrong_role` — a seeded `extra_accounts` principal holding a role
|
|
45
|
+
* other than the one a route requires; named via
|
|
46
|
+
* `ConformanceTableOptions.principals.wrong_role`.
|
|
47
|
+
* - `expired_session` — the keeper account presented via an *expired
|
|
48
|
+
* server-side session* cookie (minted by `fixture.mint_expired_session()`:
|
|
49
|
+
* a backdated `auth_session` row behind a still-valid signed cookie
|
|
50
|
+
* payload, so the authoritative DB-row expiry gate is what refuses it).
|
|
51
|
+
*/
|
|
52
|
+
export const ConformancePrincipal = z.enum([
|
|
53
|
+
'keeper',
|
|
54
|
+
'daemon',
|
|
55
|
+
'token',
|
|
56
|
+
'anonymous',
|
|
57
|
+
'fresh_non_admin',
|
|
58
|
+
'role_holder',
|
|
59
|
+
'wrong_role',
|
|
60
|
+
'expired_session',
|
|
61
|
+
]);
|
|
62
|
+
/** The request a conformance case issues. */
|
|
63
|
+
export const ConformanceCaseRequest = z.strictObject({
|
|
64
|
+
method: z.string().meta({
|
|
65
|
+
description: 'RPC method name (e.g. `admin_account_list`) or a REST auth-route suffix ' +
|
|
66
|
+
'(e.g. `/login`). A leading `/` selects the REST branch; otherwise the ' +
|
|
67
|
+
'runner resolves the RPC action from the spec registry.',
|
|
68
|
+
}),
|
|
69
|
+
params: z
|
|
70
|
+
.unknown()
|
|
71
|
+
.optional()
|
|
72
|
+
.meta({ description: 'Request params / body. Omit for nullary methods.' }),
|
|
73
|
+
as: ConformancePrincipal,
|
|
74
|
+
verb: z
|
|
75
|
+
.enum(['POST', 'GET'])
|
|
76
|
+
.optional()
|
|
77
|
+
.meta({ description: 'HTTP verb. Defaults to POST; use GET for `side_effects: false` reads.' }),
|
|
78
|
+
});
|
|
79
|
+
/** The expected response shape a conformance case asserts. */
|
|
80
|
+
export const ConformanceCaseExpectation = z.strictObject({
|
|
81
|
+
status: z.number().int().meta({ description: 'Expected HTTP status code.' }),
|
|
82
|
+
error_reason: z
|
|
83
|
+
.string()
|
|
84
|
+
.optional()
|
|
85
|
+
.meta({
|
|
86
|
+
description: 'Expected error reason — pass the IMPORTED `ERROR_*` constant from ' +
|
|
87
|
+
'`http/error_schemas.ts`, never a string literal. Asserted against the RPC ' +
|
|
88
|
+
'`error.data.reason` (when the denial carries one) or the REST flat-body ' +
|
|
89
|
+
'`error` field. The pre-validation 401 carries `data.reason` too; a denial ' +
|
|
90
|
+
'that genuinely omits it falls back to the `status` assertion to pin the class.',
|
|
91
|
+
}),
|
|
92
|
+
fields: z
|
|
93
|
+
.record(z.string(), z.unknown())
|
|
94
|
+
.optional()
|
|
95
|
+
.meta({
|
|
96
|
+
description: 'Specific field-value assertions on the success `result` (2xx) or the error ' +
|
|
97
|
+
'`error.data` (non-2xx). Each key must deep-equal the corresponding response field.',
|
|
98
|
+
}),
|
|
99
|
+
});
|
|
100
|
+
/**
|
|
101
|
+
* Marks a case as a deferred-by-design gap. The runner routes it through
|
|
102
|
+
* `xfail_until` instead of a normal `test` — visible (distinct from pass)
|
|
103
|
+
* and self-cleaning (flips red when the impl starts passing, forcing the
|
|
104
|
+
* marker's removal). Use for declared gaps (e.g. facts), never for
|
|
105
|
+
* in-scope gaps (those fail loud as a red `test`).
|
|
106
|
+
*/
|
|
107
|
+
export const ConformanceCaseXfail = z.strictObject({
|
|
108
|
+
tracking_id: z
|
|
109
|
+
.string()
|
|
110
|
+
.meta({ description: 'Tracking id for the deferred gap (issue id or tracking slug).' }),
|
|
111
|
+
reason: z.string().meta({ description: 'Why this case is deferred-by-design.' }),
|
|
112
|
+
});
|
|
113
|
+
/**
|
|
114
|
+
* A single conformance case. `name` is the assertion; the optional
|
|
115
|
+
* free-text `note` is printed in the test label / failure output. A
|
|
116
|
+
* security case's `note` should reference a **public** fuz_app doc
|
|
117
|
+
* property (`security.md` / `architecture.md` / module TSDoc), since the
|
|
118
|
+
* table ships in a public package — not an internal planning doc. The note
|
|
119
|
+
* is documentation, not a gate: it stays free-text by design because a
|
|
120
|
+
* non-empty-string check never catches a *wrong* citation — the citation
|
|
121
|
+
* is verified in review.
|
|
122
|
+
*/
|
|
123
|
+
export const ConformanceCase = z.strictObject({
|
|
124
|
+
name: z.string().meta({ description: 'The assertion, used as the test label.' }),
|
|
125
|
+
request: ConformanceCaseRequest,
|
|
126
|
+
expect: ConformanceCaseExpectation,
|
|
127
|
+
note: z
|
|
128
|
+
.string()
|
|
129
|
+
.optional()
|
|
130
|
+
.meta({ description: 'Free-text note printed in the label / failure output.' }),
|
|
131
|
+
xfail: ConformanceCaseXfail.optional(),
|
|
132
|
+
});
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
import type { AppSurfaceSpec } from '../../http/surface.js';
|
|
3
|
+
import type { SessionOptions } from '../../auth/session_cookie.js';
|
|
4
|
+
import { type RpcEndpointsSuiteOption } from '../rpc_helpers.js';
|
|
5
|
+
import { type ConformanceCase } from './conformance_case.js';
|
|
6
|
+
import type { BackendCapabilities } from './capabilities.js';
|
|
7
|
+
import type { SetupTest } from './setup.js';
|
|
8
|
+
/**
|
|
9
|
+
* Names a seeded `extra_accounts` username for the `role_holder` /
|
|
10
|
+
* `wrong_role` principals — the only two that aren't backed by an
|
|
11
|
+
* always-available fixture accessor. Suites exercising those principals
|
|
12
|
+
* declare the matching `extra_accounts` at setup and name them here.
|
|
13
|
+
*/
|
|
14
|
+
export interface ConformancePrincipalConfig {
|
|
15
|
+
/** `extra_accounts` username for the `role_holder` principal. */
|
|
16
|
+
readonly role_holder?: string;
|
|
17
|
+
/** `extra_accounts` username for the `wrong_role` principal. */
|
|
18
|
+
readonly wrong_role?: string;
|
|
19
|
+
}
|
|
20
|
+
/** Options for `describe_conformance_table_tests`. */
|
|
21
|
+
export interface ConformanceTableOptions {
|
|
22
|
+
/** The conformance cases to run, in order. */
|
|
23
|
+
readonly cases: ReadonlyArray<ConformanceCase>;
|
|
24
|
+
/** Per-test fixture producer (in-process or cross-process). */
|
|
25
|
+
readonly setup_test: SetupTest;
|
|
26
|
+
/** Surface spec — supplies the `RouteSpec`s for the REST branch. */
|
|
27
|
+
readonly surface_source: AppSurfaceSpec;
|
|
28
|
+
/** Declared backend capabilities (reserved for capability-gated rows). */
|
|
29
|
+
readonly capabilities: BackendCapabilities;
|
|
30
|
+
/** RPC endpoints — resolved to find each method's action spec. */
|
|
31
|
+
readonly rpc_endpoints: RpcEndpointsSuiteOption;
|
|
32
|
+
/** Session options — needed to resolve the `rpc_endpoints` factory form. */
|
|
33
|
+
readonly session_options: SessionOptions<string>;
|
|
34
|
+
/** Maps the `role_holder` / `wrong_role` principals to seeded usernames. */
|
|
35
|
+
readonly principals?: ConformancePrincipalConfig;
|
|
36
|
+
/** `describe` block label. Defaults to `'conformance table'`. */
|
|
37
|
+
readonly suite_name?: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Register a `describe` block running every `ConformanceCase` as a
|
|
41
|
+
* vitest `test` (or `xfail_until` for deferred-by-design rows). Drives
|
|
42
|
+
* either transport via the shared `{setup_test, surface_source,
|
|
43
|
+
* capabilities}` protocol.
|
|
44
|
+
*/
|
|
45
|
+
export declare const describe_conformance_table_tests: (options: ConformanceTableOptions) => void;
|
|
46
|
+
//# sourceMappingURL=conformance_table.d.ts.map
|
|
@@ -0,0 +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"}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import '../assert_dev_env.js';
|
|
2
|
+
/**
|
|
3
|
+
* Table runner for the declarative cross-backend conformance suite.
|
|
4
|
+
*
|
|
5
|
+
* `describe_conformance_table_tests` takes a list of `ConformanceCase`
|
|
6
|
+
* rows plus the standard `{setup_test, surface_source, capabilities}`
|
|
7
|
+
* fixture protocol every Tier 1 suite uses — so **one runner drives both
|
|
8
|
+
* transports**: in-process via `default_in_process_setup` (fast, every
|
|
9
|
+
* `gro test`) and cross-process via `default_cross_process_setup` (the
|
|
10
|
+
* conformance gate, exercising each impl's real auth resolution over real
|
|
11
|
+
* HTTP). Same case definition, transport-parameterized.
|
|
12
|
+
*
|
|
13
|
+
* Each row references a `method`; the runner resolves its `input` /
|
|
14
|
+
* `output` schema from the live spec registry (RPC) or `RouteSpec` (the 6
|
|
15
|
+
* REST auth routes) — the row never carries a schema. The principal the
|
|
16
|
+
* row runs `as` resolves to a `TestFixture` accessor via
|
|
17
|
+
* `resolve_principal` — no inline credential minting.
|
|
18
|
+
*
|
|
19
|
+
* @module
|
|
20
|
+
*/
|
|
21
|
+
import { assert, describe, test } from 'vitest';
|
|
22
|
+
import { find_auth_route, rest_auth_route_suffixes, } from '../integration_helpers.js';
|
|
23
|
+
import { find_rpc_action, rpc_call, resolve_rpc_endpoints_for_setup, } from '../rpc_helpers.js';
|
|
24
|
+
import {} from './conformance_case.js';
|
|
25
|
+
import { xfail_until } from './xfail.js';
|
|
26
|
+
/**
|
|
27
|
+
* Map a `ConformancePrincipal` onto the transport + headers it
|
|
28
|
+
* authenticates with, reading exclusively from the per-test `TestFixture`.
|
|
29
|
+
*
|
|
30
|
+
* The five always-available principals resolve from fixture accessors;
|
|
31
|
+
* `role_holder` / `wrong_role` read a seeded `extra_accounts` entry named
|
|
32
|
+
* via `options.principals` (throws a clear setup error when unconfigured).
|
|
33
|
+
*/
|
|
34
|
+
const resolve_principal = async (fixture, as, principals) => {
|
|
35
|
+
switch (as) {
|
|
36
|
+
case 'keeper':
|
|
37
|
+
// Keeper carries its session cookie via `create_session_headers`
|
|
38
|
+
// (in-process the transport is stateless; cross-process the jar
|
|
39
|
+
// also holds it — the explicit header is the same value).
|
|
40
|
+
return { transport: fixture.transport, headers: fixture.create_session_headers() };
|
|
41
|
+
case 'daemon':
|
|
42
|
+
// Daemon-token is a non-browser credential — empty jar + no Origin.
|
|
43
|
+
return {
|
|
44
|
+
transport: fixture.fresh_transport({ origin: null }),
|
|
45
|
+
headers: fixture.create_daemon_token_headers(),
|
|
46
|
+
suppress_default_origin: true,
|
|
47
|
+
};
|
|
48
|
+
case 'token':
|
|
49
|
+
// Bearer is discarded in a browser context — empty jar + no Origin.
|
|
50
|
+
return {
|
|
51
|
+
transport: fixture.fresh_transport({ origin: null }),
|
|
52
|
+
headers: fixture.create_bearer_headers(),
|
|
53
|
+
suppress_default_origin: true,
|
|
54
|
+
};
|
|
55
|
+
case 'anonymous':
|
|
56
|
+
// Fresh jar so the keeper cookie (cross-process) can't leak in.
|
|
57
|
+
return { transport: fixture.fresh_transport(), headers: {} };
|
|
58
|
+
case 'fresh_non_admin': {
|
|
59
|
+
const account = await fixture.create_account();
|
|
60
|
+
return { transport: fixture.fresh_transport(), headers: account.create_session_headers() };
|
|
61
|
+
}
|
|
62
|
+
case 'expired_session': {
|
|
63
|
+
// The keeper presented via an expired server-side session — fresh
|
|
64
|
+
// jar so only the (expired) cookie this seam returns is sent. The
|
|
65
|
+
// minted cookie payload is valid; the backdated `auth_session` row
|
|
66
|
+
// is what the DB-row expiry gate refuses.
|
|
67
|
+
const cookie = await fixture.mint_expired_session();
|
|
68
|
+
return { transport: fixture.fresh_transport(), headers: { cookie } };
|
|
69
|
+
}
|
|
70
|
+
case 'role_holder':
|
|
71
|
+
case 'wrong_role': {
|
|
72
|
+
const username = principals?.[as];
|
|
73
|
+
if (!username) {
|
|
74
|
+
throw new Error(`conformance: principal '${as}' requires options.principals.${as} naming a seeded ` +
|
|
75
|
+
`extra_accounts username (declare the account at setup via extra_accounts).`);
|
|
76
|
+
}
|
|
77
|
+
const extra = fixture.extra_accounts[username];
|
|
78
|
+
if (!extra) {
|
|
79
|
+
throw new Error(`conformance: extra_accounts['${username}'] not seeded for principal '${as}' — ` +
|
|
80
|
+
`declare it in the suite's extra_accounts option.`);
|
|
81
|
+
}
|
|
82
|
+
return { transport: fixture.fresh_transport(), headers: extra.create_session_headers() };
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
/** Assert each expected field deep-equals the corresponding response field. */
|
|
87
|
+
const assert_fields = (actual, fields, label) => {
|
|
88
|
+
assert.ok(actual !== null && typeof actual === 'object', `${label}: expected an object to read fields from, got ${JSON.stringify(actual)}`);
|
|
89
|
+
const record = actual;
|
|
90
|
+
for (const [key, expected] of Object.entries(fields)) {
|
|
91
|
+
assert.deepEqual(record[key], expected, `${label}: field '${key}'`);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const is_success_status = (status) => status >= 200 && status < 300;
|
|
95
|
+
/**
|
|
96
|
+
* Run one conformance case end-to-end: resolve the principal, dispatch the
|
|
97
|
+
* request, and assert the expected status / reason / fields.
|
|
98
|
+
*/
|
|
99
|
+
const run_case = async (c, options, resolved_rpc_endpoints) => {
|
|
100
|
+
const fixture = await options.setup_test();
|
|
101
|
+
const { transport, headers, suppress_default_origin } = await resolve_principal(fixture, c.request.as, options.principals);
|
|
102
|
+
if (c.request.method.startsWith('/')) {
|
|
103
|
+
await run_rest_case(c, options, transport, headers);
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
await run_rpc_case(c, transport, headers, suppress_default_origin, resolved_rpc_endpoints);
|
|
107
|
+
};
|
|
108
|
+
/** Dispatch + assert a case targeting an RPC method. */
|
|
109
|
+
const run_rpc_case = async (c, transport, headers, suppress_default_origin, resolved_rpc_endpoints) => {
|
|
110
|
+
const found = find_rpc_action(resolved_rpc_endpoints, c.request.method);
|
|
111
|
+
if (!found) {
|
|
112
|
+
throw new Error(`conformance: RPC method '${c.request.method}' not found on the surface — ` +
|
|
113
|
+
`check the method name or that the action is registered on rpc_endpoints.`);
|
|
114
|
+
}
|
|
115
|
+
const res = await rpc_call({
|
|
116
|
+
app: transport,
|
|
117
|
+
path: found.path,
|
|
118
|
+
method: c.request.method,
|
|
119
|
+
params: c.request.params,
|
|
120
|
+
headers,
|
|
121
|
+
...(c.request.verb && { verb: c.request.verb }),
|
|
122
|
+
...(suppress_default_origin && { suppress_default_origin: true }),
|
|
123
|
+
});
|
|
124
|
+
if (is_success_status(c.expect.status)) {
|
|
125
|
+
assert.ok(res.ok, `${c.name}: expected success (${c.expect.status}) but got error ${JSON.stringify(res.ok ? undefined : res.error)}`);
|
|
126
|
+
assert.strictEqual(res.status, c.expect.status, `${c.name}: status`);
|
|
127
|
+
const parsed = found.action.spec.output.safeParse(res.result);
|
|
128
|
+
assert.ok(parsed.success, `${c.name}: result does not match spec.output: ${JSON.stringify(parsed.success ? undefined : parsed.error.issues)}`);
|
|
129
|
+
if (c.expect.fields)
|
|
130
|
+
assert_fields(res.result, c.expect.fields, c.name);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
assert.ok(!res.ok, `${c.name}: expected error status ${c.expect.status} but got success`);
|
|
134
|
+
assert.strictEqual(res.status, c.expect.status, `${c.name}: error status`);
|
|
135
|
+
if (c.expect.error_reason !== undefined) {
|
|
136
|
+
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
|
+
}
|
|
143
|
+
}
|
|
144
|
+
if (c.expect.fields)
|
|
145
|
+
assert_fields(res.error.data, c.expect.fields, c.name);
|
|
146
|
+
};
|
|
147
|
+
/** Dispatch + assert a case targeting one of the 6 REST auth routes. */
|
|
148
|
+
const run_rest_case = async (c, options, transport, headers) => {
|
|
149
|
+
const suffix = c.request.method;
|
|
150
|
+
if (!rest_auth_route_suffixes.includes(suffix)) {
|
|
151
|
+
throw new Error(`conformance: REST method '${c.request.method}' is not a known auth-route suffix ` +
|
|
152
|
+
`(${rest_auth_route_suffixes.join(', ')}). Use an RPC method name for RPC actions.`);
|
|
153
|
+
}
|
|
154
|
+
const verb = c.request.verb ?? 'POST';
|
|
155
|
+
const route = find_auth_route(options.surface_source.route_specs, suffix, verb);
|
|
156
|
+
if (!route) {
|
|
157
|
+
throw new Error(`conformance: no REST route spec for ${verb} ${suffix} on the surface.`);
|
|
158
|
+
}
|
|
159
|
+
const init = {
|
|
160
|
+
method: verb,
|
|
161
|
+
headers: { 'Content-Type': 'application/json', ...headers },
|
|
162
|
+
...(verb !== 'GET' &&
|
|
163
|
+
c.request.params !== undefined && { body: JSON.stringify(c.request.params) }),
|
|
164
|
+
};
|
|
165
|
+
const response = await transport(route.path, init);
|
|
166
|
+
assert.strictEqual(response.status, c.expect.status, `${c.name}: status`);
|
|
167
|
+
const body = await response.json().catch(() => undefined);
|
|
168
|
+
if (is_success_status(c.expect.status)) {
|
|
169
|
+
const parsed = route.output.safeParse(body);
|
|
170
|
+
assert.ok(parsed.success, `${c.name}: body does not match route output: ${JSON.stringify(parsed.success ? undefined : parsed.error.issues)}`);
|
|
171
|
+
}
|
|
172
|
+
else if (c.expect.error_reason !== undefined) {
|
|
173
|
+
const error = body?.error;
|
|
174
|
+
assert.strictEqual(error, c.expect.error_reason, `${c.name}: body.error`);
|
|
175
|
+
}
|
|
176
|
+
if (c.expect.fields)
|
|
177
|
+
assert_fields(body, c.expect.fields, c.name);
|
|
178
|
+
};
|
|
179
|
+
/**
|
|
180
|
+
* Register a `describe` block running every `ConformanceCase` as a
|
|
181
|
+
* vitest `test` (or `xfail_until` for deferred-by-design rows). Drives
|
|
182
|
+
* either transport via the shared `{setup_test, surface_source,
|
|
183
|
+
* capabilities}` protocol.
|
|
184
|
+
*/
|
|
185
|
+
export const describe_conformance_table_tests = (options) => {
|
|
186
|
+
const resolved_rpc_endpoints = resolve_rpc_endpoints_for_setup(options.rpc_endpoints, options.session_options);
|
|
187
|
+
describe(options.suite_name ?? 'conformance table', () => {
|
|
188
|
+
for (const c of options.cases) {
|
|
189
|
+
const label = c.note ? `${c.name} — ${c.note}` : c.name;
|
|
190
|
+
const body = () => run_case(c, options, resolved_rpc_endpoints);
|
|
191
|
+
if (c.xfail) {
|
|
192
|
+
xfail_until(c.xfail.tracking_id, c.xfail.reason, label, body);
|
|
193
|
+
}
|
|
194
|
+
else {
|
|
195
|
+
test(label, body);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
};
|
|
@@ -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,
|
|
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;CAC/B;AAED;;;;;GAKG;AACH,eAAO,MAAM,8BAA8B,GAC1C,MAAM,iCAAiC,KACrC,aAmCF,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;CAC3B;AAED;;;;;GAKG;AACH,eAAO,MAAM,gCAAgC,GAC5C,MAAM,mCAAmC,KACvC,aA2CF,CAAC"}
|
|
@@ -13,7 +13,9 @@ export const ts_default_capabilities = Object.freeze({
|
|
|
13
13
|
login_rate_limit: false,
|
|
14
14
|
ws: true,
|
|
15
15
|
sse: false,
|
|
16
|
-
|
|
16
|
+
cell_crud: true,
|
|
17
|
+
cell_relations: true,
|
|
18
|
+
account_lifecycle: true,
|
|
17
19
|
});
|
|
18
20
|
/**
|
|
19
21
|
* Capabilities for the Rust family. Adds `trusted_proxy: true` (the
|
|
@@ -27,7 +29,9 @@ export const rust_default_capabilities = Object.freeze({
|
|
|
27
29
|
login_rate_limit: true,
|
|
28
30
|
ws: true,
|
|
29
31
|
sse: false,
|
|
30
|
-
|
|
32
|
+
cell_crud: true,
|
|
33
|
+
cell_relations: true,
|
|
34
|
+
account_lifecycle: true,
|
|
31
35
|
});
|
|
32
36
|
/** Bootstrap block built from the default secrets + supplied paths. */
|
|
33
37
|
const build_default_bootstrap = (paths, overrides) => ({
|
|
@@ -10,10 +10,19 @@ import type { AppServerContext } from '../../server/app_server.js';
|
|
|
10
10
|
*/
|
|
11
11
|
export declare const spine_session_options: SessionOptions<string>;
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
13
|
+
* App role the role-shaped-`cell_grant` cross suite exercises. Registered
|
|
14
|
+
* with no grant path (`grant_paths: []`) so it stays a valid registry member
|
|
15
|
+
* without entering the admin / self-service grant flows — holders are seeded
|
|
16
|
+
* directly via `extra_accounts`. Must match the `cell_editor` entry in the
|
|
17
|
+
* Rust `testing_spine_stub`'s `known_roles` (cross-language test contract).
|
|
18
|
+
*/
|
|
19
|
+
export declare const SPINE_CELL_EDITOR_ROLE = "cell_editor";
|
|
20
|
+
/**
|
|
21
|
+
* The spine's closed role registry: built-ins plus `SPINE_CELL_EDITOR_ROLE`.
|
|
22
|
+
* Threaded into the cell spec set's role-validity gate; the Rust stub mirrors
|
|
23
|
+
* the same membership. When the spine grows additional grantable roles,
|
|
24
|
+
* thread their registry through `create_role_schema` here so the admin suite
|
|
25
|
+
* picks up grant-path coverage.
|
|
17
26
|
*/
|
|
18
27
|
export declare const spine_roles: RoleSchemaResult;
|
|
19
28
|
/** RPC endpoint mount path — matches the binary's `/api/rpc`. */
|
|
@@ -27,11 +36,10 @@ export declare const SPINE_RPC_PATH = "/api/rpc";
|
|
|
27
36
|
*/
|
|
28
37
|
export declare const SPINE_SSE_PATH = "/api/admin/audit/stream";
|
|
29
38
|
/**
|
|
30
|
-
* Factory-form RPC endpoints
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
* never called across the process boundary.
|
|
39
|
+
* Factory-form RPC endpoints over the per-test `ctx.deps`. `create_app_server`
|
|
40
|
+
* (in the binary) owns live dispatch; the surface builder invokes the factory
|
|
41
|
+
* once with a stub ctx for setup-time path/method lookup, so the handler
|
|
42
|
+
* closures are never called across the process boundary.
|
|
35
43
|
*
|
|
36
44
|
* Test binaries append their own `_testing_reset` action to this endpoint's
|
|
37
45
|
* `actions` (see `testing_reset_actions.ts`); it is intentionally excluded
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"default_spine_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_spine_surface.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA6B9B,OAAO,EAAqB,KAAK,gBAAgB,EAAC,MAAM,2BAA2B,CAAC;AACpF,OAAO,EAAwB,KAAK,cAAc,EAAC,MAAM,8BAA8B,CAAC;AAGxF,OAAO,EAAqB,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAC5E,OAAO,KAAK,EAAC,cAAc,EAAE,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAC3E,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,4BAA4B,CAAC;AAGjE;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,cAAc,CAAC,MAAM,CAAwC,CAAC;AAElG
|
|
1
|
+
{"version":3,"file":"default_spine_surface.d.ts","sourceRoot":"../src/lib/","sources":["../../../src/lib/testing/cross_backend/default_spine_surface.ts"],"names":[],"mappings":"AAAA,OAAO,sBAAsB,CAAC;AA6B9B,OAAO,EAAqB,KAAK,gBAAgB,EAAC,MAAM,2BAA2B,CAAC;AACpF,OAAO,EAAwB,KAAK,cAAc,EAAC,MAAM,8BAA8B,CAAC;AAGxF,OAAO,EAAqB,KAAK,SAAS,EAAC,MAAM,0BAA0B,CAAC;AAC5E,OAAO,KAAK,EAAC,cAAc,EAAE,eAAe,EAAC,MAAM,uBAAuB,CAAC;AAC3E,OAAO,KAAK,EAAC,gBAAgB,EAAC,MAAM,4BAA4B,CAAC;AAGjE;;;GAGG;AACH,eAAO,MAAM,qBAAqB,EAAE,cAAc,CAAC,MAAM,CAAwC,CAAC;AAElG;;;;;;GAMG;AACH,eAAO,MAAM,sBAAsB,gBAAgB,CAAC;AAEpD;;;;;;GAMG;AACH,eAAO,MAAM,WAAW,EAAE,gBAExB,CAAC;AAEH,iEAAiE;AACjE,eAAO,MAAM,cAAc,aAAa,CAAC;AAEzC;;;;;;GAMG;AACH,eAAO,MAAM,cAAc,4BAA4B,CAAC;AAExD;;;;;;;;;;GAUG;AACH,eAAO,MAAM,mBAAmB,GAAI,KAAK,gBAAgB,KAAG,KAAK,CAAC,eAAe,CAOhF,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,wBAAwB,GAAI,KAAK,gBAAgB,KAAG,KAAK,CAAC,SAAS,CAiB/E,CAAC;AAEF;;;;;;;;GAQG;AACH,eAAO,MAAM,yBAAyB,QAAO,cAM1C,CAAC"}
|