@fuzdev/fuz_app 0.63.0 → 0.65.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/CLAUDE.md +525 -827
- package/dist/actions/broadcast_api.d.ts +1 -1
- package/dist/actions/broadcast_api.js +1 -1
- package/dist/actions/cancel.d.ts +2 -2
- package/dist/actions/cancel.js +3 -3
- package/dist/actions/connection_closer.d.ts +65 -0
- package/dist/actions/connection_closer.d.ts.map +1 -0
- package/dist/actions/connection_closer.js +38 -0
- package/dist/actions/register_action_ws.d.ts +2 -2
- package/dist/actions/register_action_ws.d.ts.map +1 -1
- package/dist/actions/register_action_ws.js +23 -2
- package/dist/actions/register_ws_endpoint.d.ts +12 -10
- package/dist/actions/register_ws_endpoint.d.ts.map +1 -1
- package/dist/actions/register_ws_endpoint.js +5 -5
- package/dist/actions/transports_ws_auth_guard.d.ts +25 -10
- package/dist/actions/transports_ws_auth_guard.d.ts.map +1 -1
- package/dist/actions/transports_ws_auth_guard.js +24 -9
- package/dist/actions/ws_endpoint_spec.d.ts +119 -0
- package/dist/actions/ws_endpoint_spec.d.ts.map +1 -0
- package/dist/actions/ws_endpoint_spec.js +13 -0
- package/dist/auth/CLAUDE.md +592 -1808
- package/dist/auth/account_action_specs.d.ts +1 -1
- package/dist/auth/account_actions.d.ts +13 -0
- package/dist/auth/account_actions.d.ts.map +1 -1
- package/dist/auth/account_actions.js +31 -1
- package/dist/auth/account_routes.d.ts +12 -2
- package/dist/auth/account_routes.d.ts.map +1 -1
- package/dist/auth/account_routes.js +55 -8
- package/dist/auth/account_schema.d.ts +4 -4
- package/dist/auth/account_schema.d.ts.map +1 -1
- package/dist/auth/admin_action_specs.d.ts +8 -8
- package/dist/auth/admin_actions.d.ts +11 -0
- package/dist/auth/admin_actions.d.ts.map +1 -1
- package/dist/auth/admin_actions.js +25 -0
- package/dist/auth/api_token_queries.js +1 -1
- package/dist/auth/audit_emitter.d.ts +56 -12
- package/dist/auth/audit_emitter.d.ts.map +1 -1
- package/dist/auth/audit_emitter.js +38 -12
- package/dist/auth/audit_log_ddl.d.ts +1 -1
- package/dist/auth/audit_log_ddl.d.ts.map +1 -1
- package/dist/auth/audit_log_ddl.js +1 -1
- package/dist/auth/audit_log_schema.d.ts +5 -3
- package/dist/auth/audit_log_schema.d.ts.map +1 -1
- package/dist/auth/audit_log_schema.js +5 -3
- package/dist/auth/bootstrap_account.d.ts.map +1 -1
- package/dist/auth/bootstrap_account.js +1 -5
- package/dist/auth/bootstrap_routes.d.ts +8 -2
- package/dist/auth/bootstrap_routes.d.ts.map +1 -1
- package/dist/auth/bootstrap_routes.js +15 -11
- package/dist/auth/invite_schema.d.ts +2 -2
- package/dist/auth/keyring.d.ts +6 -6
- package/dist/auth/keyring.js +8 -8
- package/dist/auth/role_grant_offer_actions.d.ts.map +1 -1
- package/dist/auth/role_grant_offer_actions.js +4 -2
- package/dist/auth/signup_routes.d.ts +1 -1
- package/dist/auth/standard_rpc_actions.d.ts +1 -0
- package/dist/auth/standard_rpc_actions.d.ts.map +1 -1
- package/dist/auth/standard_rpc_actions.js +1 -0
- package/dist/db/create_db.d.ts.map +1 -1
- package/dist/db/create_db.js +13 -0
- package/dist/dev/setup.d.ts +2 -2
- package/dist/dev/setup.js +3 -3
- package/dist/http/CLAUDE.md +225 -483
- package/dist/http/error_schemas.d.ts +0 -4
- package/dist/http/error_schemas.d.ts.map +1 -1
- package/dist/http/error_schemas.js +0 -4
- package/dist/http/ip_canonical.d.ts +100 -0
- package/dist/http/ip_canonical.d.ts.map +1 -0
- package/dist/http/ip_canonical.js +195 -0
- package/dist/http/origin.d.ts +14 -6
- package/dist/http/origin.d.ts.map +1 -1
- package/dist/http/origin.js +14 -32
- package/dist/http/pending_effects.d.ts +1 -1
- package/dist/http/pending_effects.js +1 -1
- package/dist/http/proxy.d.ts +13 -5
- package/dist/http/proxy.d.ts.map +1 -1
- package/dist/http/proxy.js +15 -23
- package/dist/http/surface.d.ts +50 -0
- package/dist/http/surface.d.ts.map +1 -1
- package/dist/http/surface.js +27 -1
- package/dist/primitive_schemas.d.ts +20 -4
- package/dist/primitive_schemas.d.ts.map +1 -1
- package/dist/primitive_schemas.js +25 -4
- package/dist/realtime/sse_auth_guard.d.ts +16 -4
- package/dist/realtime/sse_auth_guard.d.ts.map +1 -1
- package/dist/realtime/sse_auth_guard.js +15 -3
- package/dist/runtime/mock.js +1 -1
- package/dist/server/app_backend.d.ts +66 -19
- package/dist/server/app_backend.d.ts.map +1 -1
- package/dist/server/app_backend.js +57 -34
- package/dist/server/app_server.d.ts +101 -10
- package/dist/server/app_server.d.ts.map +1 -1
- package/dist/server/app_server.js +105 -6
- package/dist/server/env.d.ts +7 -7
- package/dist/server/env.d.ts.map +1 -1
- package/dist/server/env.js +14 -14
- package/dist/server/startup.d.ts.map +1 -1
- package/dist/server/startup.js +12 -0
- package/dist/server/static.d.ts +4 -4
- package/dist/server/static.js +7 -7
- package/dist/testing/CLAUDE.md +269 -59
- package/dist/testing/admin_integration.d.ts +18 -23
- package/dist/testing/admin_integration.d.ts.map +1 -1
- package/dist/testing/admin_integration.js +159 -202
- package/dist/testing/adversarial_headers.d.ts +6 -0
- package/dist/testing/adversarial_headers.d.ts.map +1 -1
- package/dist/testing/adversarial_headers.js +13 -5
- package/dist/testing/app_server.d.ts +148 -60
- package/dist/testing/app_server.d.ts.map +1 -1
- package/dist/testing/app_server.js +143 -54
- package/dist/testing/attack_surface.d.ts +8 -7
- package/dist/testing/attack_surface.d.ts.map +1 -1
- package/dist/testing/attack_surface.js +12 -8
- package/dist/testing/audit_completeness.d.ts +23 -22
- package/dist/testing/audit_completeness.d.ts.map +1 -1
- package/dist/testing/audit_completeness.js +199 -158
- package/dist/testing/audit_drift_guard.d.ts +116 -0
- package/dist/testing/audit_drift_guard.d.ts.map +1 -0
- package/dist/testing/audit_drift_guard.js +134 -0
- package/dist/testing/bootstrap_success.d.ts +28 -0
- package/dist/testing/bootstrap_success.d.ts.map +1 -0
- package/dist/testing/bootstrap_success.js +144 -0
- package/dist/testing/connection_closer_helpers.d.ts +44 -0
- package/dist/testing/connection_closer_helpers.d.ts.map +1 -0
- package/dist/testing/connection_closer_helpers.js +48 -0
- package/dist/testing/cross_backend/capabilities.d.ts +64 -0
- package/dist/testing/cross_backend/capabilities.d.ts.map +1 -0
- package/dist/testing/cross_backend/capabilities.js +47 -0
- package/dist/testing/cross_backend/setup.d.ts +215 -0
- package/dist/testing/cross_backend/setup.d.ts.map +1 -0
- package/dist/testing/cross_backend/setup.js +101 -0
- package/dist/testing/data_exposure.d.ts +14 -15
- package/dist/testing/data_exposure.d.ts.map +1 -1
- package/dist/testing/data_exposure.js +127 -146
- package/dist/testing/db_entities.d.ts +11 -1
- package/dist/testing/db_entities.d.ts.map +1 -1
- package/dist/testing/db_entities.js +13 -1
- package/dist/testing/integration.d.ts +35 -21
- package/dist/testing/integration.d.ts.map +1 -1
- package/dist/testing/integration.js +231 -293
- package/dist/testing/integration_helpers.d.ts +16 -6
- package/dist/testing/integration_helpers.d.ts.map +1 -1
- package/dist/testing/integration_helpers.js +7 -7
- package/dist/testing/mock_fs.d.ts.map +1 -1
- package/dist/testing/mock_fs.js +0 -2
- package/dist/testing/rate_limiting.d.ts.map +1 -1
- package/dist/testing/rate_limiting.js +13 -4
- package/dist/testing/role_grant_helpers.d.ts +31 -0
- package/dist/testing/role_grant_helpers.d.ts.map +1 -0
- package/dist/testing/role_grant_helpers.js +46 -0
- package/dist/testing/round_trip.d.ts +21 -16
- package/dist/testing/round_trip.d.ts.map +1 -1
- package/dist/testing/round_trip.js +65 -86
- package/dist/testing/rpc_helpers.d.ts +2 -1
- package/dist/testing/rpc_helpers.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.d.ts +24 -21
- package/dist/testing/rpc_round_trip.d.ts.map +1 -1
- package/dist/testing/rpc_round_trip.js +91 -106
- package/dist/testing/schema_introspect.d.ts +106 -0
- package/dist/testing/schema_introspect.d.ts.map +1 -0
- package/dist/testing/schema_introspect.js +123 -0
- package/dist/testing/schema_parity.d.ts +144 -0
- package/dist/testing/schema_parity.d.ts.map +1 -0
- package/dist/testing/schema_parity.js +233 -0
- package/dist/testing/sse_round_trip.d.ts.map +1 -1
- package/dist/testing/sse_round_trip.js +12 -6
- package/dist/testing/standard.d.ts +57 -25
- package/dist/testing/standard.d.ts.map +1 -1
- package/dist/testing/standard.js +62 -5
- package/dist/testing/stubs.d.ts +22 -3
- package/dist/testing/stubs.d.ts.map +1 -1
- package/dist/testing/stubs.js +28 -21
- package/dist/testing/surface_invariants.d.ts +66 -1
- package/dist/testing/surface_invariants.d.ts.map +1 -1
- package/dist/testing/surface_invariants.js +103 -1
- package/dist/testing/transports/surface_source.d.ts +51 -0
- package/dist/testing/transports/surface_source.d.ts.map +1 -0
- package/dist/testing/transports/surface_source.js +19 -0
- package/dist/ui/SurfaceExplorer.svelte +161 -2
- package/dist/ui/SurfaceExplorer.svelte.d.ts.map +1 -1
- package/package.json +4 -4
|
@@ -3,8 +3,18 @@ import './assert_dev_env.js';
|
|
|
3
3
|
* Composable audit log completeness test suite.
|
|
4
4
|
*
|
|
5
5
|
* Verifies that every auth mutation route produces the expected audit log
|
|
6
|
-
* event. Uses the real middleware stack and database
|
|
7
|
-
*
|
|
6
|
+
* event. Uses the real middleware stack and database, then **reads back
|
|
7
|
+
* through the `audit_log_list` RPC** — the production observation path the
|
|
8
|
+
* admin UI consumes. This is intentional end-to-end coverage: emit →
|
|
9
|
+
* persist → query → wire response, all in one round-trip.
|
|
10
|
+
*
|
|
11
|
+
* The trade is a deliberate transport coupling: a regression in
|
|
12
|
+
* `audit_log_list_action_spec`'s auth or response shape can surface here as
|
|
13
|
+
* a secondary failure. `describe_rpc_round_trip_tests` covers that RPC
|
|
14
|
+
* directly, so primary breakages localize there first. For *unit-level*
|
|
15
|
+
* "did the handler emit?" assertions without the persistence path, use
|
|
16
|
+
* `create_recording_audit_emitter` from `audit_drift_guard.ts` — that
|
|
17
|
+
* captures emits before they hit DB or transport.
|
|
8
18
|
*
|
|
9
19
|
* Bootstrap is excluded because it requires filesystem token state that
|
|
10
20
|
* `create_test_app` does not provide. Bootstrap audit logging is tested
|
|
@@ -13,21 +23,48 @@ import './assert_dev_env.js';
|
|
|
13
23
|
* @module
|
|
14
24
|
*/
|
|
15
25
|
import { describe, test, assert } from 'vitest';
|
|
16
|
-
import {
|
|
17
|
-
import { AUDIT_EVENT_TYPES } from '../auth/audit_log_schema.js';
|
|
18
|
-
import {
|
|
19
|
-
import { create_test_app, } from './app_server.js';
|
|
20
|
-
import { create_pglite_factory, create_describe_db, auth_integration_truncate_tables, } from './db.js';
|
|
26
|
+
import { ROLE_ADMIN } from '../auth/role_schema.js';
|
|
27
|
+
import { AUDIT_EVENT_TYPES, } from '../auth/audit_log_schema.js';
|
|
28
|
+
import {} from './app_server.js';
|
|
21
29
|
import { find_auth_route } from './integration_helpers.js';
|
|
22
|
-
import { run_migrations } from '../db/migrate.js';
|
|
23
|
-
import { query_accept_offer } from '../auth/role_grant_offer_queries.js';
|
|
24
30
|
import { rpc_call_for_spec, require_rpc_endpoint_path, resolve_rpc_endpoints_for_setup, } from './rpc_helpers.js';
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
31
|
+
import { role_grant_offer_and_accept } from './role_grant_helpers.js';
|
|
32
|
+
import { role_grant_offer_accept_action_spec, role_grant_offer_create_action_spec, role_grant_revoke_action_spec, } from '../auth/role_grant_offer_action_specs.js';
|
|
33
|
+
import { admin_session_revoke_all_action_spec, admin_token_revoke_all_action_spec, app_settings_update_action_spec, audit_log_list_action_spec, AUDIT_LOG_LIST_LIMIT_MAX, invite_create_action_spec, invite_delete_action_spec, } from '../auth/admin_action_specs.js';
|
|
27
34
|
import { account_session_list_action_spec, account_session_revoke_action_spec, account_session_revoke_all_action_spec, account_token_create_action_spec, account_token_list_action_spec, account_token_revoke_action_spec, } from '../auth/account_action_specs.js';
|
|
28
|
-
/**
|
|
29
|
-
|
|
30
|
-
|
|
35
|
+
/**
|
|
36
|
+
* Mint a dedicated admin account whose sole job is to read the audit log
|
|
37
|
+
* via RPC. Decoupling the *observer* from the *subject* keeps the helper
|
|
38
|
+
* shape uniform across every audit-touching test — even ones whose
|
|
39
|
+
* mutation revokes the bootstrapped admin's credentials (logout,
|
|
40
|
+
* session_revoke, password_change). The observer has no role-grants the
|
|
41
|
+
* test exercises and no credentials the test mutates, so it survives
|
|
42
|
+
* every flow.
|
|
43
|
+
*/
|
|
44
|
+
const create_admin_observer = (fixture) => fixture.create_account({ username: 'audit_observer', roles: [ROLE_ADMIN] });
|
|
45
|
+
/**
|
|
46
|
+
* List audit log events via the `audit_log_list` RPC. Replaces the previous
|
|
47
|
+
* raw `SELECT FROM audit_log` query — the RPC is the documented contract and
|
|
48
|
+
* the same path the admin UI consumes. The RPC orders newest-first
|
|
49
|
+
* (`ORDER BY seq DESC`); assertions use `.some()` / `.find()` so ordering is
|
|
50
|
+
* invisible to test logic. Default `limit: AUDIT_LOG_LIST_LIMIT_MAX` (200)
|
|
51
|
+
* future-proofs against tests with more emissions; per-test
|
|
52
|
+
* `auth_integration_truncate_tables` keeps the table empty between cases.
|
|
53
|
+
*
|
|
54
|
+
* `observer` is a dedicated admin account (see {@link create_admin_observer})
|
|
55
|
+
* — its credentials are never the subject of the mutation under test, so the
|
|
56
|
+
* read works uniformly across every flow including session-revoking ones.
|
|
57
|
+
*/
|
|
58
|
+
const list_audit_events = async (app, rpc_path, observer, params = {}) => {
|
|
59
|
+
const res = await rpc_call_for_spec({
|
|
60
|
+
app,
|
|
61
|
+
path: rpc_path,
|
|
62
|
+
spec: audit_log_list_action_spec,
|
|
63
|
+
params: { limit: AUDIT_LOG_LIST_LIMIT_MAX, ...params },
|
|
64
|
+
headers: observer.create_session_headers(),
|
|
65
|
+
});
|
|
66
|
+
assert.ok(res.ok, `audit_log_list failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
67
|
+
return res.result.events;
|
|
31
68
|
};
|
|
32
69
|
/** Assert that audit events contain the expected event type. */
|
|
33
70
|
const assert_has_event = (events, expected, context) => {
|
|
@@ -44,17 +81,6 @@ const assert_event_credential_type = (events, expected, credential_type, context
|
|
|
44
81
|
const recorded = (match.metadata ?? {}).credential_type;
|
|
45
82
|
assert.strictEqual(recorded, credential_type, `Expected '${expected}' audit metadata.credential_type === '${credential_type}' after ${context} (got ${JSON.stringify(recorded)})`);
|
|
46
83
|
};
|
|
47
|
-
/** Build CreateTestAppOptions with admin+keeper roles. */
|
|
48
|
-
const build_options = (options, db) => ({
|
|
49
|
-
session_options: options.session_options,
|
|
50
|
-
create_route_specs: options.create_route_specs,
|
|
51
|
-
db,
|
|
52
|
-
roles: [ROLE_KEEPER, ROLE_ADMIN],
|
|
53
|
-
app_options: {
|
|
54
|
-
...options.app_options,
|
|
55
|
-
rpc_endpoints: options.rpc_endpoints,
|
|
56
|
-
},
|
|
57
|
-
});
|
|
58
84
|
/** Headers for unauthenticated JSON requests (login, signup). */
|
|
59
85
|
const UNAUTHENTICATED_JSON_HEADERS = {
|
|
60
86
|
host: 'localhost',
|
|
@@ -62,7 +88,7 @@ const UNAUTHENTICATED_JSON_HEADERS = {
|
|
|
62
88
|
'content-type': 'application/json',
|
|
63
89
|
};
|
|
64
90
|
/** Standard request headers for session-authenticated JSON requests. */
|
|
65
|
-
const json_session_headers = (
|
|
91
|
+
const json_session_headers = (fixture, extra) => fixture.create_session_headers({
|
|
66
92
|
'content-type': 'application/json',
|
|
67
93
|
...extra,
|
|
68
94
|
});
|
|
@@ -71,7 +97,8 @@ const json_session_headers = (test_app, extra) => test_app.create_session_header
|
|
|
71
97
|
*
|
|
72
98
|
* Verifies that every auth mutation route produces the correct audit log
|
|
73
99
|
* event type. Exercises routes via HTTP requests against a real PGlite
|
|
74
|
-
* database, then
|
|
100
|
+
* database, then reads events back through the `audit_log_list` RPC
|
|
101
|
+
* (the production observation path the admin UI consumes).
|
|
75
102
|
*
|
|
76
103
|
* @throws Error at setup time when `options.rpc_endpoints` is empty — the
|
|
77
104
|
* mutation-audit tests drive role_grant flow, session/token revoke-all, and
|
|
@@ -79,294 +106,301 @@ const json_session_headers = (test_app, extra) => test_app.create_session_header
|
|
|
79
106
|
* `require_rpc_endpoint_path`.
|
|
80
107
|
*/
|
|
81
108
|
export const describe_audit_completeness_tests = (options) => {
|
|
109
|
+
if (options.surface_source.kind !== 'inline') {
|
|
110
|
+
throw new Error("describe_audit_completeness_tests requires surface_source.kind === 'inline' — " +
|
|
111
|
+
'the cross-process snapshot variant lands with the spawned-backend transport');
|
|
112
|
+
}
|
|
113
|
+
const route_specs = options.surface_source.spec.route_specs;
|
|
82
114
|
// Hard-fail early so consumers see a clear setup error instead of a
|
|
83
|
-
// confusing test failure when `rpc_endpoints` is missing.
|
|
84
|
-
// callers are resolved with a stub ctx purely to extract the endpoint
|
|
85
|
-
// path; real handlers run per-test via `app_options.rpc_endpoints`.
|
|
115
|
+
// confusing test failure when `rpc_endpoints` is missing.
|
|
86
116
|
const rpc_endpoints_for_setup = resolve_rpc_endpoints_for_setup(options.rpc_endpoints, options.session_options);
|
|
87
117
|
const rpc_path = require_rpc_endpoint_path(rpc_endpoints_for_setup);
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
};
|
|
91
|
-
const factories = options.db_factories ?? [create_pglite_factory(init_schema)];
|
|
92
|
-
const describe_db = create_describe_db(factories, auth_integration_truncate_tables);
|
|
93
|
-
describe_db('audit_log_completeness', (get_db) => {
|
|
118
|
+
void options.capabilities;
|
|
119
|
+
describe('audit_log_completeness', () => {
|
|
94
120
|
// --- Account routes ---
|
|
95
121
|
describe('account mutation audit events', () => {
|
|
96
122
|
test('login success produces login event', async () => {
|
|
97
|
-
const
|
|
98
|
-
const
|
|
123
|
+
const fixture = await options.setup_test();
|
|
124
|
+
const observer = await create_admin_observer(fixture);
|
|
125
|
+
const login_route = find_auth_route(route_specs, '/login', 'POST');
|
|
99
126
|
assert.ok(login_route, 'Expected POST /login route');
|
|
100
|
-
const res = await
|
|
127
|
+
const res = await fixture.transport(login_route.path, {
|
|
101
128
|
method: 'POST',
|
|
102
129
|
headers: UNAUTHENTICATED_JSON_HEADERS,
|
|
103
130
|
body: JSON.stringify({
|
|
104
|
-
username:
|
|
131
|
+
username: fixture.account.username,
|
|
105
132
|
password: 'test-password-123',
|
|
106
133
|
}),
|
|
107
134
|
});
|
|
108
135
|
assert.strictEqual(res.status, 200);
|
|
109
|
-
const events = await
|
|
136
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
110
137
|
assert_has_event(events, 'login', 'POST /login (success)');
|
|
111
138
|
});
|
|
112
139
|
test('login failure produces login event with failure outcome', async () => {
|
|
113
|
-
const
|
|
114
|
-
const
|
|
140
|
+
const fixture = await options.setup_test();
|
|
141
|
+
const observer = await create_admin_observer(fixture);
|
|
142
|
+
const login_route = find_auth_route(route_specs, '/login', 'POST');
|
|
115
143
|
assert.ok(login_route, 'Expected POST /login route');
|
|
116
|
-
const res = await
|
|
144
|
+
const res = await fixture.transport(login_route.path, {
|
|
117
145
|
method: 'POST',
|
|
118
146
|
headers: UNAUTHENTICATED_JSON_HEADERS,
|
|
119
147
|
body: JSON.stringify({
|
|
120
|
-
username:
|
|
148
|
+
username: fixture.account.username,
|
|
121
149
|
password: 'wrong-password',
|
|
122
150
|
}),
|
|
123
151
|
});
|
|
124
152
|
assert.strictEqual(res.status, 401);
|
|
125
|
-
const events = await
|
|
153
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
126
154
|
assert_has_event(events, 'login', 'POST /login (failure)');
|
|
127
155
|
});
|
|
128
156
|
test('logout produces logout event', async () => {
|
|
129
|
-
const
|
|
130
|
-
const
|
|
157
|
+
const fixture = await options.setup_test();
|
|
158
|
+
const observer = await create_admin_observer(fixture);
|
|
159
|
+
const logout_route = find_auth_route(route_specs, '/logout', 'POST');
|
|
131
160
|
assert.ok(logout_route, 'Expected POST /logout route');
|
|
132
|
-
const res = await
|
|
161
|
+
const res = await fixture.transport(logout_route.path, {
|
|
133
162
|
method: 'POST',
|
|
134
|
-
headers:
|
|
163
|
+
headers: fixture.create_session_headers(),
|
|
135
164
|
});
|
|
136
165
|
assert.strictEqual(res.status, 200);
|
|
137
|
-
const events = await
|
|
166
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
138
167
|
assert_has_event(events, 'logout', 'POST /logout');
|
|
139
168
|
});
|
|
140
169
|
test('token create produces token_create event', async () => {
|
|
141
|
-
const
|
|
170
|
+
const fixture = await options.setup_test();
|
|
171
|
+
const observer = await create_admin_observer(fixture);
|
|
142
172
|
const res = await rpc_call_for_spec({
|
|
143
|
-
app:
|
|
173
|
+
app: { request: fixture.transport },
|
|
144
174
|
path: rpc_path,
|
|
145
175
|
spec: account_token_create_action_spec,
|
|
146
176
|
params: { name: 'audit-test' },
|
|
147
|
-
headers:
|
|
177
|
+
headers: fixture.create_session_headers(),
|
|
148
178
|
});
|
|
149
179
|
assert.ok(res.ok, `account_token_create failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
150
|
-
const events = await
|
|
180
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
151
181
|
assert_has_event(events, 'token_create', 'account_token_create RPC');
|
|
152
182
|
assert_event_credential_type(events, 'token_create', 'session', 'account_token_create RPC');
|
|
153
183
|
});
|
|
154
184
|
test('token revoke produces token_revoke event', async () => {
|
|
155
|
-
const
|
|
185
|
+
const fixture = await options.setup_test();
|
|
186
|
+
const observer = await create_admin_observer(fixture);
|
|
156
187
|
// get a token ID to revoke
|
|
157
188
|
const list_res = await rpc_call_for_spec({
|
|
158
|
-
app:
|
|
189
|
+
app: { request: fixture.transport },
|
|
159
190
|
path: rpc_path,
|
|
160
191
|
spec: account_token_list_action_spec,
|
|
161
192
|
params: undefined,
|
|
162
|
-
headers:
|
|
193
|
+
headers: fixture.create_session_headers(),
|
|
163
194
|
});
|
|
164
195
|
assert.ok(list_res.ok, 'account_token_list should succeed');
|
|
165
196
|
const { tokens } = list_res.result;
|
|
166
197
|
assert.ok(tokens.length > 0, 'Expected at least one token');
|
|
167
198
|
const res = await rpc_call_for_spec({
|
|
168
|
-
app:
|
|
199
|
+
app: { request: fixture.transport },
|
|
169
200
|
path: rpc_path,
|
|
170
201
|
spec: account_token_revoke_action_spec,
|
|
171
202
|
params: { token_id: tokens[0].id },
|
|
172
|
-
headers:
|
|
203
|
+
headers: fixture.create_session_headers(),
|
|
173
204
|
});
|
|
174
205
|
assert.ok(res.ok, `account_token_revoke failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
175
|
-
const events = await
|
|
206
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
176
207
|
assert_has_event(events, 'token_revoke', 'account_token_revoke RPC');
|
|
177
208
|
assert_event_credential_type(events, 'token_revoke', 'session', 'account_token_revoke RPC');
|
|
178
209
|
});
|
|
179
210
|
test('session revoke produces session_revoke event', async () => {
|
|
180
|
-
const
|
|
211
|
+
const fixture = await options.setup_test();
|
|
212
|
+
const observer = await create_admin_observer(fixture);
|
|
181
213
|
// login to create a second session we can revoke
|
|
182
|
-
const login_route = find_auth_route(
|
|
214
|
+
const login_route = find_auth_route(route_specs, '/login', 'POST');
|
|
183
215
|
assert.ok(login_route, 'Expected POST /login route');
|
|
184
|
-
await
|
|
216
|
+
await fixture.transport(login_route.path, {
|
|
185
217
|
method: 'POST',
|
|
186
218
|
headers: UNAUTHENTICATED_JSON_HEADERS,
|
|
187
219
|
body: JSON.stringify({
|
|
188
|
-
username:
|
|
220
|
+
username: fixture.account.username,
|
|
189
221
|
password: 'test-password-123',
|
|
190
222
|
}),
|
|
191
223
|
});
|
|
192
|
-
// get session IDs (newest first
|
|
224
|
+
// get session IDs (newest first — `account_session_list` orders DESC
|
|
225
|
+
// by `created_at`, so [0] is the just-logged-in session and [1] is
|
|
226
|
+
// the bootstrap session driving the RPC call).
|
|
193
227
|
const list_res = await rpc_call_for_spec({
|
|
194
|
-
app:
|
|
228
|
+
app: { request: fixture.transport },
|
|
195
229
|
path: rpc_path,
|
|
196
230
|
spec: account_session_list_action_spec,
|
|
197
231
|
params: undefined,
|
|
198
|
-
headers:
|
|
232
|
+
headers: fixture.create_session_headers(),
|
|
199
233
|
});
|
|
200
234
|
assert.ok(list_res.ok, 'account_session_list should succeed');
|
|
201
235
|
const { sessions } = list_res.result;
|
|
202
236
|
assert.ok(sessions.length >= 2, 'Expected at least 2 sessions');
|
|
203
|
-
// revoke the
|
|
237
|
+
// revoke the newest session — not the bootstrap one driving auth.
|
|
204
238
|
const res = await rpc_call_for_spec({
|
|
205
|
-
app:
|
|
239
|
+
app: { request: fixture.transport },
|
|
206
240
|
path: rpc_path,
|
|
207
241
|
spec: account_session_revoke_action_spec,
|
|
208
|
-
params: { session_id: sessions[
|
|
209
|
-
headers:
|
|
242
|
+
params: { session_id: sessions[0].id },
|
|
243
|
+
headers: fixture.create_session_headers(),
|
|
210
244
|
});
|
|
211
245
|
assert.ok(res.ok, `account_session_revoke failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
212
|
-
const events = await
|
|
246
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
213
247
|
assert_has_event(events, 'session_revoke', 'account_session_revoke RPC');
|
|
214
248
|
assert_event_credential_type(events, 'session_revoke', 'session', 'account_session_revoke RPC');
|
|
215
249
|
});
|
|
216
250
|
test('session revoke-all produces session_revoke_all event', async () => {
|
|
217
|
-
const
|
|
251
|
+
const fixture = await options.setup_test();
|
|
252
|
+
const observer = await create_admin_observer(fixture);
|
|
218
253
|
const res = await rpc_call_for_spec({
|
|
219
|
-
app:
|
|
254
|
+
app: { request: fixture.transport },
|
|
220
255
|
path: rpc_path,
|
|
221
256
|
spec: account_session_revoke_all_action_spec,
|
|
222
257
|
params: undefined,
|
|
223
|
-
headers:
|
|
258
|
+
headers: fixture.create_session_headers(),
|
|
224
259
|
});
|
|
225
260
|
assert.ok(res.ok, `account_session_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
226
|
-
const events = await
|
|
261
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
227
262
|
assert_has_event(events, 'session_revoke_all', 'account_session_revoke_all RPC');
|
|
228
263
|
assert_event_credential_type(events, 'session_revoke_all', 'session', 'account_session_revoke_all RPC');
|
|
229
264
|
});
|
|
230
265
|
test('password change produces password_change event', async () => {
|
|
231
|
-
const
|
|
232
|
-
const
|
|
266
|
+
const fixture = await options.setup_test();
|
|
267
|
+
const observer = await create_admin_observer(fixture);
|
|
268
|
+
const route = find_auth_route(route_specs, '/password', 'POST');
|
|
233
269
|
assert.ok(route, 'Expected POST /password route');
|
|
234
|
-
const res = await
|
|
270
|
+
const res = await fixture.transport(route.path, {
|
|
235
271
|
method: 'POST',
|
|
236
|
-
headers: json_session_headers(
|
|
272
|
+
headers: json_session_headers(fixture),
|
|
237
273
|
body: JSON.stringify({
|
|
238
274
|
current_password: 'test-password-123',
|
|
239
275
|
new_password: 'new-password-456',
|
|
240
276
|
}),
|
|
241
277
|
});
|
|
242
278
|
assert.strictEqual(res.status, 200);
|
|
243
|
-
const events = await
|
|
279
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
244
280
|
assert_has_event(events, 'password_change', 'POST /password');
|
|
245
281
|
assert_event_credential_type(events, 'password_change', 'session', 'POST /password');
|
|
246
282
|
});
|
|
247
283
|
});
|
|
248
284
|
// --- Admin routes ---
|
|
249
285
|
describe('admin mutation audit events', () => {
|
|
250
|
-
test('admin offer (RPC) + accept produces role_grant_offer_create and role_grant_create events', async () => {
|
|
251
|
-
const
|
|
252
|
-
const
|
|
286
|
+
test('admin offer (RPC) + accept (RPC) produces role_grant_offer_create, role_grant_offer_accept, and role_grant_create events', async () => {
|
|
287
|
+
const fixture = await options.setup_test();
|
|
288
|
+
const observer = await create_admin_observer(fixture);
|
|
289
|
+
const target = await fixture.create_account({ username: 'audit_target' });
|
|
253
290
|
const offer_res = await rpc_call_for_spec({
|
|
254
|
-
app:
|
|
291
|
+
app: { request: fixture.transport },
|
|
255
292
|
path: rpc_path,
|
|
256
293
|
spec: role_grant_offer_create_action_spec,
|
|
257
294
|
params: { to_account_id: target.account.id, role: ROLE_ADMIN },
|
|
258
|
-
headers:
|
|
295
|
+
headers: fixture.create_session_headers(),
|
|
259
296
|
});
|
|
260
297
|
assert.ok(offer_res.ok, `role_grant_offer_create failed: ${offer_res.ok ? '' : JSON.stringify(offer_res.error)}`);
|
|
261
298
|
const { offer } = offer_res.result;
|
|
262
299
|
// Admin offer emits `role_grant_offer_create` only — the role_grant doesn't
|
|
263
|
-
// exist yet. Drive the accept to confirm `
|
|
264
|
-
// downstream consent transition.
|
|
265
|
-
const events_after_offer = await
|
|
300
|
+
// exist yet. Drive the accept to confirm `role_grant_offer_accept` and
|
|
301
|
+
// `role_grant_create` both fire on the downstream consent transition.
|
|
302
|
+
const events_after_offer = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
266
303
|
assert_has_event(events_after_offer, 'role_grant_offer_create', 'role_grant_offer_create RPC');
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
});
|
|
304
|
+
const accept_res = await rpc_call_for_spec({
|
|
305
|
+
app: { request: fixture.transport },
|
|
306
|
+
path: rpc_path,
|
|
307
|
+
spec: role_grant_offer_accept_action_spec,
|
|
308
|
+
params: { offer_id: offer.id },
|
|
309
|
+
headers: target.create_session_headers(),
|
|
274
310
|
});
|
|
275
|
-
|
|
276
|
-
|
|
311
|
+
assert.ok(accept_res.ok, `role_grant_offer_accept failed: ${accept_res.ok ? '' : JSON.stringify(accept_res.error)}`);
|
|
312
|
+
const events_after_accept = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
313
|
+
assert_has_event(events_after_accept, 'role_grant_offer_accept', 'offer accept RPC');
|
|
314
|
+
assert_has_event(events_after_accept, 'role_grant_create', 'offer accept RPC');
|
|
277
315
|
});
|
|
278
316
|
test('role_grant revoke (RPC) produces role_grant_revoke event with both target columns', async () => {
|
|
279
|
-
const
|
|
280
|
-
const
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const accept_result = await get_db().transaction(async (tx) => {
|
|
292
|
-
return query_accept_offer({ db: tx }, {
|
|
293
|
-
offer_id: offer.id,
|
|
294
|
-
to_account_id: target.account.id,
|
|
295
|
-
actor_id: target.actor.id,
|
|
296
|
-
ip: null,
|
|
297
|
-
});
|
|
317
|
+
const fixture = await options.setup_test();
|
|
318
|
+
const observer = await create_admin_observer(fixture);
|
|
319
|
+
const target = await fixture.create_account({ username: 'audit_revoke_target' });
|
|
320
|
+
// Offer + accept to materialize a role_grant we can revoke. The
|
|
321
|
+
// consent path itself is covered by the `offer + accept` test above;
|
|
322
|
+
// here we only need the role_grant to exist.
|
|
323
|
+
const { role_grant_id } = await role_grant_offer_and_accept({
|
|
324
|
+
app: { request: fixture.transport },
|
|
325
|
+
rpc_path,
|
|
326
|
+
grantor: fixture,
|
|
327
|
+
recipient: target,
|
|
328
|
+
role: ROLE_ADMIN,
|
|
298
329
|
});
|
|
299
330
|
// Revoke via RPC.
|
|
300
331
|
const revoke_res = await rpc_call_for_spec({
|
|
301
|
-
app:
|
|
332
|
+
app: { request: fixture.transport },
|
|
302
333
|
path: rpc_path,
|
|
303
334
|
spec: role_grant_revoke_action_spec,
|
|
304
|
-
params: { actor_id: target.actor.id, role_grant_id
|
|
305
|
-
headers:
|
|
335
|
+
params: { actor_id: target.actor.id, role_grant_id },
|
|
336
|
+
headers: fixture.create_session_headers(),
|
|
306
337
|
});
|
|
307
338
|
assert.ok(revoke_res.ok, `role_grant_revoke failed: ${revoke_res.ok ? '' : JSON.stringify(revoke_res.error)}`);
|
|
308
|
-
const events = await
|
|
339
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
309
340
|
assert_has_event(events, 'role_grant_revoke', 'role_grant_revoke RPC');
|
|
310
341
|
// Audit envelope must populate both target columns —
|
|
311
342
|
// `role_grant_revoke` is the canonical actor-bound-subject event.
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
assert.strictEqual(
|
|
316
|
-
assert.strictEqual(
|
|
343
|
+
// RPC orders newest-first, so `.find` picks up the just-emitted row.
|
|
344
|
+
const revoke = events.find((e) => e.event_type === 'role_grant_revoke');
|
|
345
|
+
assert.ok(revoke, 'Expected role_grant_revoke audit event');
|
|
346
|
+
assert.strictEqual(revoke.target_account_id, target.account.id);
|
|
347
|
+
assert.strictEqual(revoke.target_actor_id, target.actor.id);
|
|
317
348
|
});
|
|
318
349
|
test('admin session revoke-all produces session_revoke_all event', async () => {
|
|
319
|
-
const
|
|
320
|
-
const
|
|
350
|
+
const fixture = await options.setup_test();
|
|
351
|
+
const observer = await create_admin_observer(fixture);
|
|
352
|
+
const target = await fixture.create_account({ username: 'audit_sessions_target' });
|
|
321
353
|
const res = await rpc_call_for_spec({
|
|
322
|
-
app:
|
|
354
|
+
app: { request: fixture.transport },
|
|
323
355
|
path: rpc_path,
|
|
324
356
|
spec: admin_session_revoke_all_action_spec,
|
|
325
357
|
params: { account_id: target.account.id },
|
|
326
|
-
headers:
|
|
358
|
+
headers: fixture.create_session_headers(),
|
|
327
359
|
});
|
|
328
360
|
assert.ok(res.ok, `admin_session_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
329
|
-
const events = await
|
|
361
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
330
362
|
// admin session revoke-all also produces session_revoke_all
|
|
331
363
|
assert_has_event(events, 'session_revoke_all', 'admin_session_revoke_all RPC');
|
|
332
364
|
});
|
|
333
365
|
test('admin token revoke-all produces token_revoke_all event', async () => {
|
|
334
|
-
const
|
|
335
|
-
const
|
|
366
|
+
const fixture = await options.setup_test();
|
|
367
|
+
const observer = await create_admin_observer(fixture);
|
|
368
|
+
const target = await fixture.create_account({ username: 'audit_tokens_target' });
|
|
336
369
|
const res = await rpc_call_for_spec({
|
|
337
|
-
app:
|
|
370
|
+
app: { request: fixture.transport },
|
|
338
371
|
path: rpc_path,
|
|
339
372
|
spec: admin_token_revoke_all_action_spec,
|
|
340
373
|
params: { account_id: target.account.id },
|
|
341
|
-
headers:
|
|
374
|
+
headers: fixture.create_session_headers(),
|
|
342
375
|
});
|
|
343
376
|
assert.ok(res.ok, `admin_token_revoke_all failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
344
|
-
const events = await
|
|
377
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
345
378
|
assert_has_event(events, 'token_revoke_all', 'admin_token_revoke_all RPC');
|
|
346
379
|
});
|
|
347
380
|
});
|
|
348
381
|
// --- Invite RPC actions ---
|
|
349
382
|
describe('invite mutation audit events', () => {
|
|
350
383
|
test('invite create and delete produce audit events', async () => {
|
|
351
|
-
const
|
|
384
|
+
const fixture = await options.setup_test();
|
|
385
|
+
const observer = await create_admin_observer(fixture);
|
|
352
386
|
const create_res = await rpc_call_for_spec({
|
|
353
|
-
app:
|
|
387
|
+
app: { request: fixture.transport },
|
|
354
388
|
path: rpc_path,
|
|
355
389
|
spec: invite_create_action_spec,
|
|
356
390
|
params: { username: 'invited_user' },
|
|
357
|
-
headers:
|
|
391
|
+
headers: fixture.create_session_headers(),
|
|
358
392
|
});
|
|
359
393
|
assert.ok(create_res.ok, `invite_create failed: ${create_res.ok ? '' : JSON.stringify(create_res.error)}`);
|
|
360
394
|
const { invite } = create_res.result;
|
|
361
395
|
const delete_res = await rpc_call_for_spec({
|
|
362
|
-
app:
|
|
396
|
+
app: { request: fixture.transport },
|
|
363
397
|
path: rpc_path,
|
|
364
398
|
spec: invite_delete_action_spec,
|
|
365
399
|
params: { invite_id: invite.id },
|
|
366
|
-
headers:
|
|
400
|
+
headers: fixture.create_session_headers(),
|
|
367
401
|
});
|
|
368
402
|
assert.ok(delete_res.ok, `invite_delete failed: ${delete_res.ok ? '' : JSON.stringify(delete_res.error)}`);
|
|
369
|
-
const events = await
|
|
403
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
370
404
|
assert_has_event(events, 'invite_create', 'invite_create RPC');
|
|
371
405
|
assert_has_event(events, 'invite_delete', 'invite_delete RPC');
|
|
372
406
|
});
|
|
@@ -374,36 +408,42 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
374
408
|
// --- App settings RPC action ---
|
|
375
409
|
describe('app settings mutation audit events', () => {
|
|
376
410
|
test('settings update produces app_settings_update event', async () => {
|
|
377
|
-
const
|
|
411
|
+
const fixture = await options.setup_test();
|
|
412
|
+
const observer = await create_admin_observer(fixture);
|
|
378
413
|
const res = await rpc_call_for_spec({
|
|
379
|
-
app:
|
|
414
|
+
app: { request: fixture.transport },
|
|
380
415
|
path: rpc_path,
|
|
381
416
|
spec: app_settings_update_action_spec,
|
|
382
417
|
params: { open_signup: true },
|
|
383
|
-
headers:
|
|
418
|
+
headers: fixture.create_session_headers(),
|
|
384
419
|
});
|
|
385
420
|
assert.ok(res.ok, `app_settings_update failed: ${res.ok ? '' : JSON.stringify(res.error)}`);
|
|
386
|
-
const events = await
|
|
421
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
387
422
|
assert_has_event(events, 'app_settings_update', 'app_settings_update RPC');
|
|
388
423
|
});
|
|
389
424
|
});
|
|
390
425
|
// --- Signup route ---
|
|
391
426
|
describe('signup audit events', () => {
|
|
392
427
|
test('signup produces signup event', async () => {
|
|
393
|
-
const
|
|
428
|
+
const fixture = await options.setup_test();
|
|
429
|
+
// signup is optional — consumers that don't wire `POST /signup` (e.g.
|
|
430
|
+
// admin-only apps) skip this audit check; signup completeness for
|
|
431
|
+
// surfaces that DO wire it is still asserted by COVERED_EVENT_TYPES
|
|
432
|
+
// below. Mirrors `integration.ts`'s signup-block presence-gate.
|
|
433
|
+
const signup_route = find_auth_route(route_specs, '/signup', 'POST');
|
|
434
|
+
if (!signup_route)
|
|
435
|
+
return;
|
|
436
|
+
const observer = await create_admin_observer(fixture);
|
|
394
437
|
// enable open signup via RPC
|
|
395
438
|
const settings_res = await rpc_call_for_spec({
|
|
396
|
-
app:
|
|
439
|
+
app: { request: fixture.transport },
|
|
397
440
|
path: rpc_path,
|
|
398
441
|
spec: app_settings_update_action_spec,
|
|
399
442
|
params: { open_signup: true },
|
|
400
|
-
headers:
|
|
443
|
+
headers: fixture.create_session_headers(),
|
|
401
444
|
});
|
|
402
445
|
assert.ok(settings_res.ok, `app_settings_update failed: ${settings_res.ok ? '' : JSON.stringify(settings_res.error)}`);
|
|
403
|
-
|
|
404
|
-
const signup_route = find_auth_route(test_app.route_specs, '/signup', 'POST');
|
|
405
|
-
assert.ok(signup_route, 'Expected POST /signup route');
|
|
406
|
-
const res = await test_app.app.request(signup_route.path, {
|
|
446
|
+
const res = await fixture.transport(signup_route.path, {
|
|
407
447
|
method: 'POST',
|
|
408
448
|
headers: UNAUTHENTICATED_JSON_HEADERS,
|
|
409
449
|
body: JSON.stringify({
|
|
@@ -412,7 +452,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
412
452
|
}),
|
|
413
453
|
});
|
|
414
454
|
assert.strictEqual(res.status, 200);
|
|
415
|
-
const events = await
|
|
455
|
+
const events = await list_audit_events({ request: fixture.transport }, rpc_path, observer);
|
|
416
456
|
assert_has_event(events, 'signup', 'POST /signup');
|
|
417
457
|
});
|
|
418
458
|
});
|
|
@@ -433,6 +473,7 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
433
473
|
'token_revoke',
|
|
434
474
|
'token_revoke_all',
|
|
435
475
|
'role_grant_offer_create',
|
|
476
|
+
'role_grant_offer_accept',
|
|
436
477
|
'role_grant_create',
|
|
437
478
|
'role_grant_revoke',
|
|
438
479
|
'invite_create',
|
|
@@ -442,8 +483,9 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
442
483
|
/** Event types excluded with justification. */
|
|
443
484
|
const EXCLUDED_EVENT_TYPES = new Set([
|
|
444
485
|
'bootstrap', // requires filesystem token — tested in bootstrap_account.db.test.ts
|
|
445
|
-
// The remaining `role_grant_offer_*` events fire only via
|
|
446
|
-
//
|
|
486
|
+
// The remaining `role_grant_offer_*` events fire only via terminal
|
|
487
|
+
// transitions (decline, retract) or downstream effects (supersede on
|
|
488
|
+
// accept of a sibling, or as a fan-out of `role_grant_revoke`). Direct
|
|
447
489
|
// coverage lives in `role_grant_offer_queries.db.test.ts`,
|
|
448
490
|
// `role_grant_offer_actions.db.test.ts`,
|
|
449
491
|
// `role_grant_offer_actions.notifications.db.test.ts`, and
|
|
@@ -451,7 +493,6 @@ export const describe_audit_completeness_tests = (options) => {
|
|
|
451
493
|
// `role_grant_offer_expire` fires from the cleanup sweep
|
|
452
494
|
// (`cleanup_expired_role_grant_offers` in `auth/cleanup.ts`) —
|
|
453
495
|
// covered in `cleanup.db.test.ts`.
|
|
454
|
-
'role_grant_offer_accept',
|
|
455
496
|
'role_grant_offer_decline',
|
|
456
497
|
'role_grant_offer_retract',
|
|
457
498
|
'role_grant_offer_expire',
|