command_tower 0.10.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (189) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -3
  3. data/app/auth/command_tower/auth/auth_context.rb +10 -4
  4. data/app/controllers/command_tower/admin/application_controller.rb +27 -0
  5. data/app/controllers/command_tower/admin/audit/events_controller.rb +61 -0
  6. data/app/controllers/command_tower/admin/messaging/announcements_controller.rb +1 -8
  7. data/app/controllers/command_tower/admin/users/identities_controller.rb +77 -0
  8. data/app/controllers/command_tower/admin/users/impersonation_sessions_controller.rb +33 -0
  9. data/app/controllers/command_tower/admin/users/roles_controller.rb +40 -0
  10. data/app/controllers/command_tower/admin/users_controller.rb +46 -0
  11. data/app/controllers/command_tower/admin/workspace_controller.rb +15 -0
  12. data/app/controllers/command_tower/application_controller.rb +17 -1
  13. data/app/controllers/command_tower/auth/impersonation_session_controller.rb +27 -0
  14. data/app/controllers/command_tower/auth/logout_controller.rb +3 -1
  15. data/app/controllers/command_tower/auth/principal_capabilities_controller.rb +19 -0
  16. data/app/controllers/command_tower/me/audit_events_controller.rb +59 -0
  17. data/app/controllers/concerns/command_tower/api/application_response_renderer.rb +5 -0
  18. data/app/controllers/concerns/command_tower/auth/authentication_boundary.rb +3 -2
  19. data/app/controllers/concerns/command_tower/execution/http_boundary.rb +65 -0
  20. data/app/deserializers/command_tower/deserializers/admin/scope_parameter.rb +49 -0
  21. data/app/deserializers/command_tower/deserializers/admin/users.rb +218 -0
  22. data/app/deserializers/command_tower/deserializers/audit/events.rb +224 -0
  23. data/app/deserializers/command_tower/deserializers/clients/types.rb +27 -0
  24. data/app/errors/command_tower/errors/auth/admin_unavailable_during_impersonation_error.rb +17 -0
  25. data/app/errors/command_tower/errors/auth/default_membership_assignment_error.rb +17 -0
  26. data/app/errors/command_tower/errors/auth/impersonation_session_expired_error.rb +17 -0
  27. data/app/errors/command_tower/errors/auth/impersonation_session_missing_error.rb +21 -0
  28. data/app/errors/command_tower/errors/auth/nested_impersonation_error.rb +17 -0
  29. data/app/errors/command_tower/errors/auth/self_impersonation_error.rb +21 -0
  30. data/app/errors/command_tower/errors/continuation_exhausted_error.rb +25 -0
  31. data/app/jobs/command_tower/application_job.rb +7 -0
  32. data/app/jobs/command_tower/execution/job_boundary.rb +19 -0
  33. data/app/mailers/command_tower/application_mailer.rb +11 -1
  34. data/app/mailers/command_tower/messaging/channel_mailer.rb +1 -2
  35. data/app/models/command_tower/audit/event.rb +36 -0
  36. data/app/models/command_tower/impersonation/session.rb +51 -0
  37. data/app/serializers/command_tower/serializers/admin/users.rb +69 -0
  38. data/app/serializers/command_tower/serializers/admin/workspace/manifest_serializer.rb +58 -0
  39. data/app/serializers/command_tower/serializers/audit/events/filter_options_serializer.rb +44 -0
  40. data/app/serializers/command_tower/serializers/audit/events.rb +42 -0
  41. data/app/serializers/command_tower/serializers/auth/principal_capabilities_serializer.rb +15 -0
  42. data/app/serializers/command_tower/serializers/auth/session_response_serializer.rb +26 -2
  43. data/app/serializers/command_tower/serializers/impersonation/session_serializer.rb +19 -0
  44. data/app/services/command_tower/README.md +4 -10
  45. data/app/services/command_tower/authorize/validate.rb +7 -4
  46. data/app/services/command_tower/jwt/authenticate_user.rb +8 -2
  47. data/app/services/command_tower/jwt/authentication_outcome.rb +5 -4
  48. data/app/services/command_tower/jwt/login_create.rb +7 -4
  49. data/app/services/command_tower/messaging/accept/operation_logger.rb +3 -3
  50. data/app/services/command_tower/messaging/contract/observability/correlation.rb +3 -1
  51. data/app/services/command_tower/messaging/contract/observability/operation_logger.rb +3 -3
  52. data/app/services/command_tower/messaging/contract/observability/publisher.rb +44 -0
  53. data/app/services/command_tower/messaging/execution/operation_logger.rb +1 -1
  54. data/app/services/command_tower/messaging/handoff/operation_logger.rb +1 -1
  55. data/app/services/command_tower/messaging/inbox/operation_logger.rb +1 -1
  56. data/app/services/command_tower/messaging/planner/operation_logger.rb +1 -1
  57. data/app/services/command_tower/messaging/recipient_readiness/evaluate.rb +1 -1
  58. data/app/services/command_tower/service_base.rb +30 -43
  59. data/app/services/command_tower/service_logging.rb +2 -36
  60. data/app/services/command_tower/services/account/clear_phone.rb +8 -0
  61. data/app/services/command_tower/services/account/phone_verification/verify.rb +1 -0
  62. data/app/services/command_tower/services/account/update_phone.rb +9 -1
  63. data/app/services/command_tower/services/admin/users.rb +377 -0
  64. data/app/services/command_tower/services/admin/workspace/manifest.rb +49 -0
  65. data/app/services/command_tower/services/application_service.rb +37 -0
  66. data/app/services/command_tower/services/audit/events/filter_options.rb +75 -0
  67. data/app/services/command_tower/services/audit/events.rb +199 -0
  68. data/app/services/command_tower/services/auth/assign_default_membership_role.rb +44 -0
  69. data/app/services/command_tower/services/auth/authenticate_session.rb +33 -6
  70. data/app/services/command_tower/services/auth/email_verification/verify.rb +2 -0
  71. data/app/services/command_tower/services/auth/password_reset/reset.rb +13 -3
  72. data/app/services/command_tower/services/auth/plain_text/login.rb +9 -1
  73. data/app/services/command_tower/services/auth/principal_capabilities/project.rb +48 -0
  74. data/app/services/command_tower/services/impersonation/create.rb +29 -0
  75. data/app/services/command_tower/services/impersonation/end.rb +28 -0
  76. data/app/services/command_tower/services/impersonation/record_activity.rb +30 -0
  77. data/app/services/command_tower/services/impersonation/terminate_open_sessions.rb +51 -0
  78. data/app/services/command_tower/services/me/change_password.rb +12 -0
  79. data/app/shared_sequences/command_tower/shared_sequences/admin/users/resolve_scoped_user.rb +42 -0
  80. data/app/workflows/command_tower/transactional.rb +65 -0
  81. data/app/workflows/command_tower/workflows/admin/messaging/create_announcement_workflow.rb +11 -0
  82. data/app/workflows/command_tower/workflows/admin/scope_resolution.rb +17 -0
  83. data/app/workflows/command_tower/workflows/admin/users/error_mapping.rb +26 -0
  84. data/app/workflows/command_tower/workflows/admin/users/identity_mutation.rb +30 -0
  85. data/app/workflows/command_tower/workflows/admin/users/list_assignable_roles_workflow.rb +30 -0
  86. data/app/workflows/command_tower/workflows/admin/users/list_workflow.rb +54 -0
  87. data/app/workflows/command_tower/workflows/admin/users/set_email_validated_workflow.rb +45 -0
  88. data/app/workflows/command_tower/workflows/admin/users/show_workflow.rb +49 -0
  89. data/app/workflows/command_tower/workflows/admin/users/update_email_workflow.rb +45 -0
  90. data/app/workflows/command_tower/workflows/admin/users/update_name_workflow.rb +49 -0
  91. data/app/workflows/command_tower/workflows/admin/users/update_roles_workflow.rb +61 -0
  92. data/app/workflows/command_tower/workflows/admin/users/update_username_workflow.rb +45 -0
  93. data/app/workflows/command_tower/workflows/admin/workspace/manifest_workflow.rb +25 -0
  94. data/app/workflows/command_tower/workflows/application_workflow.rb +163 -24
  95. data/app/workflows/command_tower/workflows/audit/error_mapping.rb +24 -0
  96. data/app/workflows/command_tower/workflows/audit/events/filter_options_for_admin_workflow.rb +34 -0
  97. data/app/workflows/command_tower/workflows/audit/events/filter_options_for_user_workflow.rb +34 -0
  98. data/app/workflows/command_tower/workflows/audit/events/list_for_admin_workflow.rb +74 -0
  99. data/app/workflows/command_tower/workflows/audit/events/list_for_user_workflow.rb +45 -0
  100. data/app/workflows/command_tower/workflows/audit/events/show_for_admin_workflow.rb +54 -0
  101. data/app/workflows/command_tower/workflows/audit/events/show_for_user_workflow.rb +42 -0
  102. data/app/workflows/command_tower/workflows/auth/authenticate_request_workflow.rb +3 -2
  103. data/app/workflows/command_tower/workflows/auth/authentication_response_effects.rb +5 -1
  104. data/app/workflows/command_tower/workflows/auth/logout_workflow.rb +41 -1
  105. data/app/workflows/command_tower/workflows/auth/plain_text/login_workflow.rb +2 -0
  106. data/app/workflows/command_tower/workflows/auth/principal_capabilities/show_workflow.rb +27 -0
  107. data/app/workflows/command_tower/workflows/auth/register_workflow.rb +56 -24
  108. data/app/workflows/command_tower/workflows/auth/session/show_workflow.rb +15 -1
  109. data/app/workflows/command_tower/workflows/auth/session_error_status.rb +1 -0
  110. data/app/workflows/command_tower/workflows/impersonation/error_mapping.rb +27 -0
  111. data/app/workflows/command_tower/workflows/impersonation/start_workflow.rb +97 -0
  112. data/app/workflows/command_tower/workflows/impersonation/stop_workflow.rb +66 -0
  113. data/app/workflows/command_tower/workflows/me/update_name_workflow.rb +1 -0
  114. data/app/workflows/command_tower/workflows/profile/show_workflow.rb +1 -0
  115. data/app/workflows/command_tower/workflows/workflow_result.rb +31 -7
  116. data/config/routes.rb +20 -0
  117. data/db/migrate/20260816000001_create_command_tower_audit_events.rb +45 -0
  118. data/db/migrate/20260817000001_add_scope_columns_to_command_tower_audit_events.rb +34 -0
  119. data/db/migrate/20260817000003_create_command_tower_impersonation_sessions.rb +26 -0
  120. data/docs/admin_workspace.md +158 -0
  121. data/docs/api_reference.md +175 -14
  122. data/docs/architecture.md +5 -0
  123. data/docs/audit.md +195 -0
  124. data/docs/authentication.md +14 -0
  125. data/docs/authentication_authorization_guide.md +15 -11
  126. data/docs/authorization.md +25 -4
  127. data/docs/controllers.md +7 -1
  128. data/docs/eventing.md +179 -0
  129. data/docs/extending.md +32 -1
  130. data/docs/host_integration_guide.md +103 -12
  131. data/docs/initializing.md +2 -2
  132. data/docs/messaging_integration_guide.md +1 -1
  133. data/docs/models.md +4 -0
  134. data/docs/pagination.md +2 -2
  135. data/docs/principal_capabilities.md +89 -0
  136. data/docs/upgrades/0.10.0.md +2 -2
  137. data/docs/upgrades/0.11.0.md +92 -0
  138. data/docs/upgrades/README.md +1 -0
  139. data/lib/command_tower/admin_scope/apply_audit_scoping.rb +45 -0
  140. data/lib/command_tower/admin_scope/apply_users_narrowing.rb +21 -0
  141. data/lib/command_tower/admin_scope/manifest_projection.rb +102 -0
  142. data/lib/command_tower/admin_scope/resolve.rb +31 -0
  143. data/lib/command_tower/admin_scope/scope_context.rb +7 -0
  144. data/lib/command_tower/admin_scope/scope_option.rb +7 -0
  145. data/lib/command_tower/admin_scope.rb +20 -0
  146. data/lib/command_tower/admin_workspace.rb +16 -0
  147. data/lib/command_tower/audit/attribution.rb +90 -0
  148. data/lib/command_tower/audit/emit.rb +113 -0
  149. data/lib/command_tower/audit/masking.rb +41 -0
  150. data/lib/command_tower/audit/payload.rb +128 -0
  151. data/lib/command_tower/audit/persistence/subscriber.rb +85 -0
  152. data/lib/command_tower/audit.rb +27 -0
  153. data/lib/command_tower/authorization/assignable_roles.rb +36 -0
  154. data/lib/command_tower/authorization/default.yml +99 -5
  155. data/lib/command_tower/authorization/effective_entity_grants.rb +55 -0
  156. data/lib/command_tower/authorization/entity.rb +15 -5
  157. data/lib/command_tower/authorization/role.rb +5 -4
  158. data/lib/command_tower/authorization.rb +71 -10
  159. data/lib/command_tower/configuration/admin_scope/config.rb +104 -0
  160. data/lib/command_tower/configuration/admin_scope/tool_registration.rb +28 -0
  161. data/lib/command_tower/configuration/authorization/config.rb +6 -0
  162. data/lib/command_tower/configuration/config.rb +20 -0
  163. data/lib/command_tower/configuration/impersonation/config.rb +38 -0
  164. data/lib/command_tower/configuration/registry/admin_workspace/config.rb +199 -0
  165. data/lib/command_tower/configuration/registry/admin_workspace/tool_definition.rb +150 -0
  166. data/lib/command_tower/configuration/registry/audit/config.rb +403 -0
  167. data/lib/command_tower/configuration/registry/audit/event_definition.rb +155 -0
  168. data/lib/command_tower/configuration/registry/config.rb +34 -0
  169. data/lib/command_tower/configuration/registry/principal_capabilities/capability_definition.rb +43 -0
  170. data/lib/command_tower/configuration/registry/principal_capabilities/config.rb +136 -0
  171. data/lib/command_tower/current.rb +12 -0
  172. data/lib/command_tower/engine.rb +19 -4
  173. data/lib/command_tower/events.rb +184 -0
  174. data/lib/command_tower/execution.rb +111 -0
  175. data/lib/command_tower/impersonation/activity_declaration.rb +23 -0
  176. data/lib/command_tower/impersonation/apply_overlay.rb +88 -0
  177. data/lib/command_tower/impersonation/clear_overlay_for_audit.rb +23 -0
  178. data/lib/command_tower/impersonation/establish_identity.rb +44 -0
  179. data/lib/command_tower/install/baseline.rb +3 -0
  180. data/lib/command_tower/logging/lifecycle_declaration.rb +27 -0
  181. data/lib/command_tower/logging/projection.rb +67 -0
  182. data/lib/command_tower/logging/subscriber.rb +103 -0
  183. data/lib/command_tower/principal_capabilities.rb +15 -0
  184. data/lib/command_tower/version.rb +1 -1
  185. data/lib/command_tower.rb +12 -0
  186. data/spec/factories/impersonation_session.rb +17 -0
  187. data/spec/factories/user.rb +16 -0
  188. metadata +112 -2
  189. data/app/services/command_tower/messaging/contract/observability/structured_logger.rb +0 -56
@@ -61,6 +61,7 @@ Paths are engine-relative. Full request/response shapes: [api_reference.md](api_
61
61
  | Session | `GET /auth/session` |
62
62
  | Logout | `POST /auth/logout` |
63
63
  | Current account | `GET /me`, `GET /profile` |
64
+ | Principal capabilities | `GET /auth/principal-capabilities` |
64
65
  | Name | `PATCH /me/name` |
65
66
  | Password change | `PATCH /me/password` |
66
67
  | Identity policy | `GET /auth/identity-policy` |
@@ -92,25 +93,24 @@ Validate/reset use the emailed reset token in the body (public). See [password_r
92
93
 
93
94
  `lib/command_tower/authorization/default.yml`:
94
95
 
95
- - **`owner`** — `entities: true` (full access)
96
- - **`admin`** — entity `admin_messaging_announcements` on `Admin::Messaging::AnnouncementsController#create`
96
+ - **`owner`** — `entities: true` (full access). Explicit top-level authority; distinct from host operational Admin roles.
97
97
 
98
- There are no engine default roles named `admin-read-only`, `admin-without-impersonation`, or impersonation APIs.
98
+ There is **no** CommandTower operational `admin` role. Admin capabilities are CT-owned **entities** (`admin_workspace`, `admin_users`, `admin_impersonation`, `admin_audit_events`, `admin_messaging_announcements`, …) that hosts grant to their own roles. Impersonation start is `admin_impersonation`; it is not included in dummy `admin` / `operations_admin`. Nested impersonation is forbidden at HTTP **418** while overlaying (workflow nested 403 if reached). Stop is a session primitive (`DELETE /auth/impersonation-session`), not an Admin Users mutation. Other Admin resource endpoints return **418** `admin_unavailable_during_impersonation` during overlay except `GET /admin/workspace` (tools disabled).
99
99
 
100
100
  ### Host RBAC file (required for Me/Auth)
101
101
 
102
- `AuthorizeRequest` fails closed when controller actions lack entity mappings. Hosts must supply RBAC YAML (default path `config/rbac_groups.yml`):
102
+ `AuthorizeRequest` fails closed when the caller’s roles do not grant the CT-owned entity for the action. CommandTower ships those entity definitions. Hosts supply `config/rbac_groups.yml` with **product roles** that grant entity **names** (default path already `config/rbac_groups.yml`):
103
103
 
104
104
  ```ruby
105
105
  CommandTower.configure do |c|
106
106
  c.authorization.rbac_group_path = Rails.root.join("config/rbac_groups.yml")
107
+ c.authorization.default_membership_role = "member"
107
108
  end
108
109
  ```
109
110
 
110
- Dummy host example: `rails_app/config/rbac_groups.yml` — defines a **`member`** group and entities for session, me, profile, inbox, preferences, phone, pushover, email verification. Do not redefine groups that already exist in `default.yml` (for example `admin`); add entities and attach them carefully.
111
-
112
- Admin announcements: assign users the `admin` role (or another role that includes `admin_messaging_announcements`).
111
+ Dummy host example: `rails_app/config/rbac_groups.yml` — **`member`** grants session, me, profile, inbox, preferences, phone, pushover, email verification; operator roles grant selected Admin entities; a host-owned **`admin`** may deliberately grant a broad Admin bundle. Do not redefine `owner` or copy CT controller/entity blocks. Hosts may define an `admin` role as host policy.
113
112
 
113
+ Admin announcements: assign users a host role that includes `admin_messaging_announcements` (for example a host `admin` or `messaging_operator`).
114
114
  ### Host controller recipe
115
115
 
116
116
  ```ruby
@@ -124,17 +124,21 @@ class Host::ThingsController < ApplicationController
124
124
  end
125
125
  ```
126
126
 
127
- Map host controllers to RBAC entities the same way engine controllers are mapped in the dummy host file.
127
+ Map host controllers to **host-owned** RBAC entities in the host YAML. Grant CT-owned entity names to product roles; do not copy engine controller mappings.
128
128
 
129
129
  ## Engine admin HTTP
130
130
 
131
- Only:
132
-
133
131
  ```http
132
+ GET /admin/workspace
133
+ GET /admin/users
134
+ GET /admin/users/:id
135
+ POST /admin/users/:id/impersonation-sessions
136
+ GET /admin/audit-events
134
137
  POST /admin/messaging/announcements
138
+ DELETE /auth/impersonation-session
135
139
  ```
136
140
 
137
- No SchemaHelper admin user list, attribute modify, role assign, or impersonate routes. User administration is host/ops (`command_tower:users:create`, host tooling, etc.).
141
+ Impersonation is a session overlay, not a User mutation and not an Admin Workspace tool. There is no role-assign or attribute-modify admin surface.
138
142
 
139
143
  ## Email verification gate
140
144
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  Authorization establishes **permission** after authentication. Failed authorization returns `403`.
4
4
 
5
- Host `rbac_groups.yml` is a **required integration step** (Step 4) — see [Host integration](host_integration_guide.md#step-4--host-rbac-required). Without it, authenticated Me/Auth calls fail closed.
5
+ Host `rbac_groups.yml` is a **required integration step** (Step 4) — see [Host integration](host_integration_guide.md#step-4--host-rbac-required). CommandTower ships CT-owned entity definitions. Hosts grant those names to product roles; they must not copy CT controller mappings. Without a host role that grants Me/Auth entities, authenticated calls return **403**.
6
6
 
7
7
  ## Quick usage (host provisional)
8
8
 
@@ -14,20 +14,41 @@ before_action :authorize_user!
14
14
 
15
15
  Engine controllers use `authorize_request!` instead (AuthorizationBoundary) and return the application envelope on failure.
16
16
 
17
+ ## Capability ownership vs role composition
18
+
19
+ **Platform owns capabilities.** CommandTower defines RBAC entities (Me/Auth surfaces and Admin capabilities such as `admin_workspace`, `admin_users`, `admin_impersonation`, `admin_audit_events`, `admin_messaging_announcements`).
20
+
21
+ **Hosts own privilege bundles.** Operational/admin roles are host-defined groups that grant selected CT entities (and optional host entities). Installing CommandTower does **not** automatically grant operational Admin access.
22
+
17
23
  ## Roles and host mapping
18
24
 
19
25
  Engine defaults in `lib/command_tower/authorization/default.yml`:
20
26
 
21
- - `owner` — full access (`entities: true`)
22
- - `admin` `admin_messaging_announcements` only
27
+ - `owner` — explicit full access (`entities: true`). Distinct from host-defined operational Admin roles.
28
+ - CT-owned **entities** for Me, session, profile, inbox, preferences, phone, Pushover, email verification, admin announcements, admin audit events, Admin Users, impersonation start, and the Admin Workspace manifest
29
+ - **No** CommandTower operational `admin` role. New Admin entities require explicit host grants and must not accumulate into a platform Admin bundle.
30
+
31
+ Hosts grant `admin_impersonation` explicitly. Dummy `admin` / `operations_admin` do **not** include it; `impersonation_operator` does. `owner` keeps `entities: true`.
32
+
33
+ Stop / return-to-self (`DELETE /auth/impersonation-session`) is a session primitive: it authenticates the administrator JWT and does not authorize `admin_impersonation` on the effective (target) user.
34
+
35
+ While overlaying, Admin resource endpoints other than `GET /admin/workspace` return **418** `admin_unavailable_during_impersonation`. Workspace remains allowed so the operator can see disabled tiles. Impersonation start consumes Admin Resource Scoping as the sole target-visibility contract.
36
+
37
+ Hosts define product roles (for example `member`, `audit_operator`, or a deliberate host-owned `admin`) that **grant entity names**. See dummy host `rails_app/config/rbac_groups.yml`. Do not redefine `owner` or CT **entity** identifiers. A host may define a broad `admin` role as **host policy**; that is allowed. Composition is additive; conflicts fail at boot.
38
+
39
+ Optional: `config.authorization.default_membership_role = "member"` assigns that composed role atomically on register.
40
+
41
+ ## Principal capabilities (FE projection)
23
42
 
24
- Hosts **must** map Me/Auth controller actions in host YAML (`CommandTower.config.authorization.rbac_group_path`, default `config/rbac_groups.yml`) or authorization fails closed. See dummy host `rails_app/config/rbac_groups.yml` (`member` entities).
43
+ `GET /auth/principal-capabilities` returns possessed **frontend-projectable** ids from effective entity grants `config.registry.principal_capabilities`. Groups compose privilege; they are never the FE gating contract. See [Principal capabilities](principal_capabilities.md).
25
44
 
26
45
  ## Where to go next
27
46
 
28
47
  | Need | Guide |
29
48
  |------|-------|
30
49
  | Full RBAC entities, roles, failure shapes | [Authentication & authorization guide](authentication_authorization_guide.md) |
50
+ | Frontend-projectable capability ids | [Principal capabilities](principal_capabilities.md) |
51
+ | Admin Workspace least privilege | [Admin Workspace](admin_workspace.md) |
31
52
  | Authentication | [Authentication](authentication.md) |
32
53
  | Install / config | [Initializing](initializing.md) |
33
54
 
data/docs/controllers.md CHANGED
@@ -8,11 +8,15 @@ This page is an **index** of route areas. Detailed request/response contracts li
8
8
 
9
9
  | Area | Prefix (engine-relative) | Purpose |
10
10
  |------|--------------------------|---------|
11
- | Auth session | `/auth/*` | Login, logout, session, register, signup-session, identity policy, availability |
11
+ | Auth session | `/auth/*` | Login, logout, session, register, signup-session, identity policy, principal-capabilities, availability |
12
12
  | Email verification | `/auth/email-verification/*` | Send / verify email codes |
13
13
  | Password recovery | `/auth/password-recovery-session`, `/auth/password-reset/*` | Forgot / reset password |
14
14
  | Me / profile | `/me`, `/profile`, `/me/name`, `/me/password` | Account reads and updates |
15
15
  | Inbox | `/me/inbox*` | User inbox consume (list, open, archive, bulk ops) |
16
+ | Audit events | `/me/audit-events`, `/admin/audit-events` | User and admin audit history reads (masked) |
17
+ | Admin Users | `/admin/users` | Read-only Admin user list/show |
18
+ | Impersonation | `/admin/users/:id/impersonation-sessions`, `/auth/impersonation-session` | Start overlay; stop / return-to-self |
19
+ | Admin Workspace | `/admin/workspace` | RBAC-filtered admin tool manifest |
16
20
  | Preferences | `/me/preferences*` | Notification preferences |
17
21
  | Phone | `/me/phone*` | Phone endpoint + verification |
18
22
  | Pushover | `/me/pushover*` | Pushover endpoint lifecycle + verification |
@@ -27,6 +31,8 @@ Engine controllers are transport adapters: authenticate/authorize as required, d
27
31
  ## Related
28
32
 
29
33
  - [API reference](api_reference.md) — endpoint catalog
34
+ - [Principal capabilities](principal_capabilities.md) — FE-projectable possession projection
35
+ - [Admin Workspace](admin_workspace.md) — registry and manifest
30
36
  - [Messaging](messaging_integration_guide.md) — messaging surfaces summary
31
37
  - [Extending](extending.md) — host extension boundaries
32
38
  - [Initializing](initializing.md) — mount and configure
data/docs/eventing.md ADDED
@@ -0,0 +1,179 @@
1
+ # Eventing
2
+
3
+ CommandTower publishes internal events on **one** Rails-native network: `ActiveSupport::Notifications`. Hosts and platform code do not invent a second bus.
4
+
5
+ Logging is a **consumer** of that network. CommandTower supplies structured Hash fields to `Rails.logger`. The host Rails logger/formatter/tagged/broadcast configuration owns the final representation. CommandTower does not JSON-encode log lines.
6
+
7
+ **Event emission is exhaustive; log materialization is selective.** Lifecycle started/completed pairs always publish. **Workflow** completed success/deferred logs to Rails `info` by default (`ApplicationWorkflow` declares `log_lifecycle!`). **Service** completed success stays quiet unless the service class opts in. `started` never materializes. Envelope `log_lifecycle` means “success may be materialized,” not “emit only when true.” Subscribers must not `const_get(subject)`.
8
+
9
+ Audit persistence is a **raising** subscriber on `command_tower.audit.*` (not on lifecycle, and not the logging subscriber). **Audit authoring** (`audit(...)`, registered policy, structured emission) is documented in [Audit](audit.md).
10
+
11
+ ## Grammar
12
+
13
+ Instrument names are the subscription keys:
14
+
15
+ ```text
16
+ command_tower.<category>.<name...>
17
+ ```
18
+
19
+ - `command_tower` is the platform prefix. Hosts do **not** prepend it themselves.
20
+ - `category` is one token: `\A[a-z][a-z0-9_]*\z`. Categories are **extensible**, not a closed enum.
21
+ - `name` is one or more tokens of the same shape, joined by `.`.
22
+
23
+ Do **not** publish a generic `command_tower.event` name and dispatch on `payload[:type]`.
24
+
25
+ Do **not** use `ActiveSupport::Subscriber#attach_to` as the taxonomy driver. That convention is reversed `event.namespace` (for example `sql.active_record`). CommandTower names are `command_tower.<category>.<name...>`. Subscribe with the full string or a prefix regexp.
26
+
27
+ ### Lifecycle (automatic)
28
+
29
+ | Instrument name |
30
+ |-----------------|
31
+ | `command_tower.lifecycle.workflow.started` |
32
+ | `command_tower.lifecycle.workflow.completed` |
33
+ | `command_tower.lifecycle.service.started` |
34
+ | `command_tower.lifecycle.service.completed` |
35
+
36
+ Every `ApplicationWorkflow` `.call` / `call_from_job` invocation and every `ServiceBase` Interactor invocation emits one started/completed pair. Nested workflow→service shares `execution_uuid` and emits four events (two pairs).
37
+
38
+ ### Semantic (deliberate)
39
+
40
+ ```text
41
+ command_tower.<category>.<name...>
42
+ ```
43
+
44
+ Example: `command_tower.audit.wager_transition` via **registered** `audit(:wager_transition, ...)` — see [Audit](audit.md). Generic scalar `publish_event` remains available for non-audit categories; nested audit `changes` require `audit(...)` / `Events.publish_audit`.
45
+
46
+ ```ruby
47
+ publish_event(category: :messaging, name: :welcome_produce_failed, payload: { code: "x" })
48
+ ```
49
+
50
+ `publish_event` is on `CommandTower::Execution::ContextAccess` (workflows and services inherit it). It delegates to `CommandTower::Events.publish`. Do not include a separate Eventing module.
51
+
52
+ ## Subscribe
53
+
54
+ Rails 8.1 Fanout matches the **full instrument string**.
55
+
56
+ Exact:
57
+
58
+ ```ruby
59
+ ActiveSupport::Notifications.subscribe("command_tower.lifecycle.workflow.completed") { |*args| }
60
+ ```
61
+
62
+ Broad (lifecycle category):
63
+
64
+ ```ruby
65
+ ActiveSupport::Notifications.subscribe(/\Acommand_tower\.lifecycle(?:\.|\z)/) { |*args| }
66
+ ```
67
+
68
+ ## Envelope
69
+
70
+ Payload is a frozen Hash of scalars (and frozen arrays of scalars). Never `CommandTower::Current`, User, request, AuthContext, JWT, cookies, or exception objects.
71
+
72
+ | Field | When |
73
+ |-------|------|
74
+ | `event_uuid` | every event (new UUID at publish; **not** `execution_uuid`) |
75
+ | `subject` | class name string |
76
+ | `layer` | `:workflow` / `:service` (lifecycle only) |
77
+ | `execution_uuid`, `correlation_id`, `request_id`, `causation_id`, `source` | Execution Context snapshot |
78
+ | `user_id`, `effective_user_id`, `originating_administrator_id`, `impersonation_active` | snapshot |
79
+ | `outcome` | completed only: `:success` / `:deferred` / `:failure` / `:error` |
80
+ | `duration_ms` | completed only (monotonic clock from matching started) |
81
+ | `error_class`, `error_codes` | completed failure/error; codes only, no messages/PII |
82
+ | caller keys | semantic publish only; unsafe objects are dropped |
83
+
84
+ `event_uuid` ≠ `execution_uuid`. There is no parent/child event graph. Nested work shares `execution_uuid` only.
85
+
86
+ The snapshot is taken at publish time and is frozen against later `Current` mutation.
87
+
88
+ ## Publisher
89
+
90
+ ```ruby
91
+ CommandTower::Events.publish(category:, name:, payload: {}, subject: nil, layer: nil)
92
+ CommandTower::Events.snapshot
93
+ ```
94
+
95
+ `publish` builds the instrument name, merges snapshot + `event_uuid` + sanitized caller payload, then calls `ActiveSupport::Notifications.instrument` **without a block**. Business work is **not** yielded to `instrument`.
96
+
97
+ ## Subscriber failure (Rails-native)
98
+
99
+ CommandTower uses ASN as a **synchronous** event network. `Events.publish` does **not** wrap `instrument` in `rescue StandardError`.
100
+
101
+ > CommandTower preserves Rails-native synchronous `ActiveSupport::Notifications` publication semantics. Subscriber failure policy belongs to the subscriber/capability consuming the event. Best-effort consumers must protect their own failure boundary. Correctness-sensitive subscriber semantics are defined by the owning capability.
102
+
103
+ The generic publisher does **not** impose either policy.
104
+
105
+ | Consumer | Policy owner |
106
+ |----------|----------------|
107
+ | Logging subscriber | **This slice** — best-effort; rescues materialization failures |
108
+ | Metrics subscriber | future (likely self-protecting) |
109
+ | Audit subscriber | later phase (may be correctness-sensitive) |
110
+ | Other semantic subscribers | owning capability |
111
+
112
+ The logging subscriber (`CommandTower::Logging::Subscriber` < `ActiveSupport::LogSubscriber`) subscribes with prefix regexes (not `attach_to`) to:
113
+
114
+ - `command_tower.lifecycle.*`
115
+ - `command_tower.log.*`
116
+ - `command_tower.messaging.*`
117
+
118
+ It does **not** subscribe to `audit` or `metric`. It calls `Rails.logger` with a Hash. Host formatters decide text vs JSON vs tagged output.
119
+
120
+ ### Lifecycle Rails logs
121
+
122
+ `ApplicationWorkflow` declares `log_lifecycle!`, so workflow **completed** success/deferred materializes at `info` by default. Services stay quiet unless they opt in. Use `disable_lifecycle_logging!` on a workflow subclass to suppress its completed success line.
123
+
124
+ ```ruby
125
+ class NoisyService < ApplicationService
126
+ log_lifecycle!
127
+ end
128
+
129
+ class QuietWorkflow < ApplicationWorkflow
130
+ disable_lifecycle_logging!
131
+ end
132
+ ```
133
+
134
+ `lifecycle_loggable?` walks superclasses when unset. `log_lifecycle:` is passed into `Events.around_execution` as a **boolean scalar**. `started` is never materialized.
135
+
136
+ ### Severity and materialization
137
+
138
+ | Event | Materialize? | Level when materialized |
139
+ |-------|----------------|-------------------------|
140
+ | lifecycle started | **no** (even with opt-in — completed has outcome/duration) | — |
141
+ | lifecycle completed success / deferred | only if `payload[:log_lifecycle]` | `info` |
142
+ | lifecycle completed failure | **always** | payload `log_level` or `:info` |
143
+ | lifecycle completed error | **always** | `error` |
144
+ | `command_tower.log.<level>` | **always** | that level |
145
+ | `command_tower.messaging.*` | unchanged (3.3) | payload `log_level` or `:info` |
146
+
147
+ Authorization (`Authorize::Validate`) success diagnostics (“No Authorization required”, “User Roles”, per-role Authorized/Reason) publish `command_tower.log.debug`. Denial publishes `command_tower.log.warn`.
148
+
149
+ ### Events are comprehensive. Logs are projections.
150
+
151
+ ASN payloads remain full Execution Context snapshots (`event_uuid`, nils, `layer`, `log_lifecycle`, duplicate identity fields). The logging subscriber **projects** a new Hash for `Rails.logger`. It does not mutate the canonical payload and does not define the event contract.
152
+
153
+ **Core log fields** (when present): `event`, `subject`, `execution_uuid`, `correlation_id`, lifecycle `outcome`, `duration_ms`.
154
+
155
+ **Conditional:** `user_id` when present; `request_id` when present and different from `correlation_id`; `source` when not `:http`; `causation_id` / `originating_administrator_id` when present; `effective_user_id` when different from `user_id`; `impersonation_active` only when `true`; `error_class` / `error_codes` when present.
156
+
157
+ **Omitted from ordinary logs:** nils; `event_uuid`; `layer`; `log_lifecycle`; payload `log_level` (Rails severity already carries level); default `impersonation_active: false`; HTTP `source`.
158
+
159
+ Semantic events (`command_tower.messaging.*`, `command_tower.log.*`) keep the same common context plus remaining safe scalar fields (`message`, `channel`, `provider`, `attempt`, …). Host formatters still own JSON / tags / LGTM representation; CommandTower does not promise a Rails JSON schema.
160
+
161
+ `ApplicationError#log_level` is copied onto completed failure events as a scalar.
162
+
163
+ `log_debug` / `log_info` / `log_warn` / `log_error` publish `command_tower.log.*`. Messaging OperationLoggers publish `command_tower.messaging.*`. Delivery LogAdapters remain a **transport** (`Rails.logger.info` of a send), not observation.
164
+
165
+ Direct `Rails.logger` remains allowed for JWT decode/authenticate, boot/lib/controllers, and LogAdapters. Workflows/services must not write lifecycle observation directly.
166
+
167
+ A raising **logging** subscriber does not fail the business action. A raising **non-logging** test/audit subscriber still surfaces through `Events.publish`.
168
+
169
+ Emission and consumption remain separate: logger silence / level skips **materialization**, never ASN publication.
170
+
171
+ Architecture specs guard these seams: one `CommandTower::Current`, kernel-owned lifecycle, `Events.publish` as the ASN publisher, curated log projection, and an explicit `Rails.logger` allowlist in JWT/LogAdapter infrastructure.
172
+
173
+ ## Kernel behavior (when subscribers do not raise)
174
+
175
+ - `ApplicationWorkflow.call`: unexpected `StandardError` still becomes InternalError **after** completed `outcome: :error`. `validate_retry_strategy!` stays outside instrumentation.
176
+ - `call_from_job` / `invoke_for_job`: still **re-raise** after completed `outcome: :error`.
177
+ - Services: Interactor success → `:success`; `Interactor::Failure` → `:failure` then re-raise; unexpected exception → `:error` then re-raise. Services have no deferred outcome.
178
+
179
+ Do not wrap instance `#call` in addition to class entry (duplicate pairs). Do not also wrap `ApplicationService.call` (the Interactor `around` is the service pair).
data/docs/extending.md CHANGED
@@ -17,7 +17,7 @@ Layer map: [architecture.md](architecture.md). Install/configure/migrate/doctor:
17
17
  | `Communications::Produce` / `ProduceMany` | Sending messaging |
18
18
  | `FactoryBot.modify` | Extend shared factories |
19
19
  | Model reopen | Product associations / behavior |
20
- | Initializers | Configuration |
20
+ | Initializers | Configuration, including `config.registry.audit.event`, `config.registry.admin_workspace.tool`, and `config.registry.principal_capabilities.capability` |
21
21
  | Notification catalogs / channel policy | Host-owned messaging customization |
22
22
 
23
23
  ## Internal platform — do not extend
@@ -97,8 +97,39 @@ Do not create one Messaging workflow per message type. Do not call the messaging
97
97
 
98
98
  No DoubleFloor Me (or other product) source is required to use the platform.
99
99
 
100
+ ## Execution Context
101
+
102
+ Greenfield hosts inherit CommandTower-owned execution-boundary bases so HTTP and jobs establish ambient context automatically:
103
+
104
+ ```ruby
105
+ class ApplicationController < CommandTower::ApplicationController
106
+ end
107
+
108
+ class ApplicationJob < CommandTower::ApplicationJob
109
+ end
110
+ ```
111
+
112
+ `CommandTower::Current` is the single `CurrentAttributes` bag (`execution_uuid`, `correlation_id`, `request_id`, `source`, identity scalars). Workflows and services **read** it (`execution_context`); they do not mint a new execution per call.
113
+
114
+ `CommandTower::Auth::RequestContext` remains the HTTP request/response JWT transport handle. It is not Execution Context.
115
+
116
+ Rake/console (and other non-HTTP/job entry points) wrap work with:
117
+
118
+ ```ruby
119
+ CommandTower.with_execution(source: :rake) do
120
+ # ...
121
+ end
122
+ ```
123
+
124
+ Nested `with_execution` shares the outer context. Do not wrap `db:migrate` or doctor.
125
+
126
+ If a host **cannot** change `ApplicationController` / `ApplicationJob` superclasses, include `CommandTower::Execution::HttpBoundary` / `CommandTower::Execution::JobBoundary` as a compatibility escape hatch. Do not include those modules into every `ActionController` or `ActiveJob::Base`.
127
+
128
+ Automatic workflow/service lifecycle notifications are emitted by CommandTower kernels. Logging is a subscriber: emission is exhaustive; materialization is selective; log records are curated projections. The host owns format. See [Eventing](eventing.md).
129
+
100
130
  ## Related
101
131
 
132
+ - [Eventing](eventing.md)
102
133
  - [Architecture](architecture.md)
103
134
  - [Controllers](controllers.md)
104
135
  - [Sensitive changes](sensitive_routes.md)
@@ -37,6 +37,24 @@ Re-run `bin/rails command_tower:doctor`. Details: [Initializing — Configuratio
37
37
 
38
38
  Dummy-host reference: [`rails_app/config/initializers/command_tower.rb`](../rails_app/config/initializers/command_tower.rb).
39
39
 
40
+ ### Email / SMTP
41
+
42
+ CommandTower owns ActionMailer delivery for engine mailers (email verification, password reset, messaging channel mail).
43
+
44
+ | Environment | Delivery |
45
+ |-------------|----------|
46
+ | `test` | `:test` (in-memory `ActionMailer::Base.deliveries`; no external SMTP) |
47
+ | development / production | `:smtp` unless the host explicitly sets `config.email.delivery_method` |
48
+
49
+ Non-secret knobs live on `config.email.*` (defaults: `smtp.gmail.com`, port `587`, `plain`, STARTTLS auto). Secrets come from Credential Resolution:
50
+
51
+ - `config.credentials.smtp.user_name` / `password`, or
52
+ - ENV `GMAIL_USER_NAME` / `GMAIL_PASSWORD`
53
+
54
+ `SmtpActionMailerBridge` merges resolved credentials into `action_mailer.smtp_settings` at the end of `CommandTower.configure`. Missing credentials fail at send when `raise_delivery_errors` is true. `From` uses the resolved SMTP username when present.
55
+
56
+ Doctor does **not** probe SMTP connectivity.
57
+
40
58
  ## Step 3 — Confirm mount path
41
59
 
42
60
  Ensure routes include something like:
@@ -47,35 +65,108 @@ mount CommandTower::Engine => "/" # or "/api"
47
65
 
48
66
  Engine paths below are **relative to that mount**. Route area index: [Controllers](controllers.md).
49
67
 
68
+ ## Step 3b — Inherit execution-boundary bases
69
+
70
+ Greenfield hosts should inherit CommandTower bases so Execution Context is established automatically:
71
+
72
+ ```ruby
73
+ class ApplicationController < CommandTower::ApplicationController
74
+ end
75
+
76
+ class ApplicationJob < CommandTower::ApplicationJob
77
+ end
78
+ ```
79
+
80
+ Unauthenticated host endpoints (for example health checks) still receive HTTP `execution_uuid` / `correlation_id`. Successful authentication enriches the **same** context with `user_id` / `effective_user_id`.
81
+
82
+ Workflows and services consume `CommandTower::Current` (or `execution_context`); they do not establish a new execution. For Rake/console, use `CommandTower.with_execution(source: :rake) { ... }`.
83
+
84
+ `Auth::RequestContext` is JWT request/response transport, not Execution Context. Details: [Extending — Execution Context](extending.md#execution-context).
85
+
86
+ Lifecycle and semantic events: [Eventing](eventing.md).
87
+
88
+ If superclass inheritance is technically blocked, include `CommandTower::Execution::HttpBoundary` / `JobBoundary` on the host bases. That is an escape hatch, not the preferred contract.
89
+
50
90
  ## Step 4 — Host RBAC (required)
51
91
 
52
- AuthorizeRequest **fails closed**. Without host entity mappings, authenticated calls to `/me`, `/auth/session`, inbox, etc. return **403**.
92
+ AuthorizeRequest **fails closed**. CommandTower ships **CT-owned** entity definitions (Me, session, inbox, Admin Workspace capabilities, …) and the platform full-access role (`owner`) in `lib/command_tower/authorization/default.yml`. CommandTower does **not** ship an operational `admin` role.
93
+
94
+ The host YAML (default `config/rbac_groups.yml`) is a **second source**. Composition is additive. Hosts:
95
+
96
+ 1. Define product roles (typically `member`).
97
+ 2. **Grant names** of already-defined CT entities to those roles.
98
+ 3. Optionally define **host-owned** entities for **host** controllers.
99
+ 4. Deliberately compose operational Admin roles (least privilege or a broad host-owned `admin`).
53
100
 
54
- The configure generator does **not** create this file.
101
+ Do **not** copy CommandTower controller/entity blocks into the host file. Do **not** redefine `owner` or CT entity names. A host **may** define an `admin` group as host policy. Conflicts fail at boot (no last-write-wins).
55
102
 
56
- 1. Copy and adapt the dummy host file: [`rails_app/config/rbac_groups.yml`](../rails_app/config/rbac_groups.yml) → host `config/rbac_groups.yml`.
57
- 2. Keep a host group such as `member` with entities for session, me, profile, inbox, preferences, phone, pushover, email verification.
58
- 3. Do **not** redefine engine groups that already exist in `default.yml` (`owner`, `admin`).
59
- 4. Point config at the file if needed:
103
+ Dummy host grants-only example: [`rails_app/config/rbac_groups.yml`](../rails_app/config/rbac_groups.yml).
60
104
 
61
105
  ```ruby
62
106
  CommandTower.configure do |c|
63
107
  c.authorization.rbac_group_path = Rails.root.join("config/rbac_groups.yml")
108
+ c.authorization.default_membership_role = "member" # optional; nil disables
64
109
  end
65
110
  ```
66
111
 
67
- (Default path is already `config/rbac_groups.yml`.)
112
+ `default_membership_role` is validated against the **composed** graph at boot. Unknown names fail configuration finalization.
68
113
 
69
114
  More: [Authorization](authorization.md), [Authentication & authorization guide](authentication_authorization_guide.md).
70
115
 
116
+ Product audit names (later) register additively:
117
+
118
+ ```ruby
119
+ CommandTower.configure do |c|
120
+ c.registry.audit.event :wager_placed do |event|
121
+ event.allowed_changes = %i[status]
122
+ event.user_history = true
123
+ end
124
+ end
125
+ ```
126
+
127
+ Do **not** redefine CommandTower-owned audit names. Audit rows persist in CommandTower after `command_tower:install:migrations` and `db:migrate`. See [Audit](audit.md).
128
+
129
+ Admin Workspace tools register additively (navigation metadata, not a dispatcher):
130
+
131
+ ```ruby
132
+ CommandTower.configure do |c|
133
+ c.registry.admin_workspace.tool :host_example do |tool|
134
+ tool.label = "Example"
135
+ tool.description = "Short launcher explanation of what this tool does."
136
+ tool.route = "/admin/example"
137
+ tool.group = :product
138
+ tool.sort_order = 300
139
+ tool.required_entity = :host_example_entity
140
+ end
141
+ end
142
+ ```
143
+
144
+ Do **not** redefine CommandTower-owned tool ids (`users`, `audit`, `messaging`). Hosts own copy for host tools (`description` soft ≤100 / hard ≤160). Manifest: [Admin Workspace](admin_workspace.md).
145
+
146
+ Principal capabilities register additively (frontend-projectable ids, not a dump of all entities):
147
+
148
+ ```ruby
149
+ CommandTower.configure do |c|
150
+ c.registry.principal_capabilities.capability :host_example do |capability|
151
+ capability.required_entity = :host_example_entity
152
+ end
153
+ end
154
+ ```
155
+
156
+ Do **not** redefine CommandTower-owned capability ids (`admin_workspace`, `admin_users`, `admin_impersonation`, `admin_audit_events`, `admin_messaging_announcements`). Projection: [Principal capabilities](principal_capabilities.md). Grant `admin_impersonation` only to roles that should start impersonation of visible users. Dummy `admin` does not include it.
157
+
71
158
  ## Step 5 — Roles on users
72
159
 
73
160
  RBAC YAML defines what a role **may** do. Users still need that role assigned.
74
161
 
75
- - Specs and the dummy pattern use role name **`member`** for Me/Auth surfaces.
76
- - Engine register does **not** automatically attach `member`. Hosts must assign roles via product logic, ops (`command_tower:users:create`), or host hooks after account creation.
77
- - Admin announcements need a role that includes `admin_messaging_announcements` (engine `admin` group, or an equivalent host mapping).
78
-
162
+ - Specs and the dummy pattern use role name **`member`** for Me/Auth surfaces (including entity `principal_capabilities`).
163
+ - When `authorization.default_membership_role` is set (for example `"member"`), register assigns that role **in the same transaction** as user create. Failure rolls back the user.
164
+ - When it is `nil`, register does not attach roles. Hosts may assign via product logic, ops (`command_tower:users:create`), or other host workflows.
165
+ - Installing CommandTower does **not** grant operational Admin access. Hosts must deliberately grant Admin entities.
166
+ - Admin announcements need a host role that includes `admin_messaging_announcements`.
167
+ - Admin Workspace manifest needs a host role that includes `admin_workspace`. Tool visibility still depends on each tool's `required_entity`.
168
+ - `GET /auth/principal-capabilities` needs entity `principal_capabilities` on the caller’s roles (grant on `member` like session/me). Possessed Admin projectables still depend on the Admin entity grants above.
169
+ - A host may define a broad `admin` role that grants many Admin entities — that is host policy, not a CommandTower default.
79
170
  ## Step 6 — Enable feature gates you need
80
171
 
81
172
  When a gate is off, the route is **not drawn** → **404**.
@@ -101,7 +192,7 @@ Engine HTTP success/error bodies use the application envelope `{ data, meta, err
101
192
 
102
193
  With gates and RBAC in place (paths relative to your mount):
103
194
 
104
- 1. `POST /auth/register` (always drawn) — create a user, then **assign `member`** (Step 5) if not done by host logic.
195
+ 1. `POST /auth/register` (always drawn) — with `default_membership_role` configured, the user is created **and** assigned that role in one transaction.
105
196
  2. `POST /auth/plain-text/login` (if enabled) — receive `data.token`.
106
197
  3. `GET /me` with Bearer token — expect **200** and envelope `data` (account payload).
107
198
 
data/docs/initializing.md CHANGED
@@ -21,8 +21,8 @@ bin/rails command_tower:doctor
21
21
 
22
22
  Continue with [Host integration](host_integration_guide.md):
23
23
 
24
- 1. Host `rbac_groups.yml` (required — otherwise authenticated Me/Auth calls **403**)
25
- 2. Assign host roles (for example `member`) on users
24
+ 1. Host `rbac_groups.yml` product role that **grants** CT-owned Me/Auth entity names (required — otherwise authenticated Me/Auth calls **403**)
25
+ 2. Set `authorization.default_membership_role` (for example `"member"`) or assign roles some other supported way
26
26
  3. Enable feature gates you need (login, reset, availability, …)
27
27
  4. Smoke-check `GET /me` with a Bearer token
28
28
  5. Wire messaging catalog/adapters when you emit or use phone/Pushover
@@ -74,7 +74,7 @@ Pagination detail: [pagination.md](pagination.md). Full catalog: [api_reference.
74
74
  - Notification catalog content (registered into CommandTower notification types)
75
75
  - `platform_enabled_channels` / channel policy injection
76
76
  - Messaging adapter credentials (email / SMS / Pushover) via initializer or ENV
77
- - Host `rbac_groups.yml` entities for Me inbox/preferences/phone/pushover (and admin if used)
77
+ - Host product roles that grant CT-owned Me inbox/preferences/phone/pushover entities (and `admin` if used)
78
78
  - Product-specific operational tooling (announce rake tasks, welcome copy)
79
79
 
80
80
  ## Related
data/docs/models.md CHANGED
@@ -44,6 +44,10 @@ end
44
44
 
45
45
  Engine-owned messaging persistence (communications, endpoints, preferences, and related records) ships with CommandTower. Prefer platform services/workflows for mutations; do not dual-author schema in the host ([Initializing](initializing.md)).
46
46
 
47
+ ## Audit ledger
48
+
49
+ `CommandTower::Audit::Event` is the append-only platform ledger (`command_tower_audit_events`). Only `CommandTower::Audit::Persistence::Subscriber` writes rows. There are no foreign keys to users. Envelope `changes` persist as `change_set`. See [Audit](audit.md).
50
+
47
51
  ## Related
48
52
 
49
53
  - [Architecture](architecture.md)
data/docs/pagination.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Pagination
2
2
 
3
- CommandTower exposes one live HTTP list contract that returns pagination metadata: **Me Inbox**.
3
+ CommandTower exposes live HTTP list contracts that return pagination metadata: **Me Inbox**, **audit event lists** (`GET /me/audit-events`, `GET /admin/audit-events`), and **Admin Users** (`GET /admin/users`). These lists reuse the Inbox `limit` / `offset` / `totalCount` contract.
4
4
 
5
5
  Back to [README](../README.md).
6
6
 
@@ -38,7 +38,7 @@ CommandTower.configure do |c|
38
38
  end
39
39
  ```
40
40
 
41
- There is no engine admin list endpoint and no `pagination=true` / `page` / `cursor` query API on current engine HTTP surfaces.
41
+ There is no `pagination=true` / `page` / `cursor` query API on current engine HTTP surfaces. Admin audit list uses the same limit/offset contract as Inbox.
42
42
 
43
43
  ## Related
44
44