command_tower 0.17.0 → 0.18.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.
- checksums.yaml +4 -4
- data/README.md +8 -8
- data/app/controllers/command_tower/me/inbox_controller.rb +16 -0
- data/app/deserializers/command_tower/deserializers/messaging/inbox.rb +85 -0
- data/app/jobs/command_tower/messaging/communications/produce_recipient_job.rb +2 -0
- data/app/models/command_tower/messaging/communication.rb +31 -0
- data/app/serializers/command_tower/serializers/messaging/inbox.rb +43 -0
- data/app/services/command_tower/messaging/accept/coordinator.rb +16 -1
- data/app/services/command_tower/messaging/accept/persister.rb +13 -0
- data/app/services/command_tower/messaging/contract/mappers/communication_mapper.rb +7 -0
- data/app/services/command_tower/messaging/contract/results/communication_result.rb +1 -0
- data/app/services/command_tower/messaging/inbox/conversation_result.rb +31 -0
- data/app/services/command_tower/messaging/inbox/entry_result.rb +36 -0
- data/app/services/command_tower/messaging/inbox/reader.rb +231 -18
- data/app/services/command_tower/messaging/inbox.rb +4 -0
- data/app/services/command_tower/messaging/rendering/inbox_document_renderer.rb +53 -36
- data/app/services/command_tower/messaging/rendering/inbox_presentation_resolver.rb +52 -0
- data/app/services/command_tower/messaging/rendering/inbox_presentation_snapshot.rb +64 -0
- data/app/services/command_tower/messaging.rb +5 -1
- data/app/services/command_tower/services/messaging/communications/produce.rb +4 -0
- data/app/services/command_tower/services/messaging/communications/produce_many.rb +6 -0
- data/app/services/command_tower/services/messaging/inbox.rb +47 -1
- data/app/workflows/command_tower/workflows/messaging/communications/produce_recipient_workflow.rb +5 -1
- data/app/workflows/command_tower/workflows/messaging/inbox.rb +15 -1
- data/config/routes.rb +1 -0
- data/db/migrate/20261010000001_add_inbox_presentation_snapshot_to_messaging_communications.rb +7 -0
- data/db/migrate/20261010000002_add_conversation_identity_to_messaging_communications.rb +10 -0
- data/docs/authorization.md +1 -1
- data/docs/bootstrap/00-ownership.md +77 -0
- data/docs/bootstrap/01-docker-make-compose.md +267 -0
- data/docs/bootstrap/02-create-the-rails-app.md +75 -0
- data/docs/bootstrap/03-pin-the-gem.md +53 -0
- data/docs/bootstrap/04-secrets-and-env.md +59 -0
- data/docs/bootstrap/05-install-migrate-doctor.md +74 -0
- data/docs/bootstrap/06-mount-and-health.md +53 -0
- data/docs/bootstrap/07-execution-bases.md +38 -0
- data/docs/bootstrap/08-initializer.md +90 -0
- data/docs/bootstrap/09-rbac.md +83 -0
- data/docs/bootstrap/10-roles-and-gates.md +62 -0
- data/docs/bootstrap/11-auth-client-path.md +57 -0
- data/docs/bootstrap/12-smoke-check.md +81 -0
- data/docs/bootstrap/13-optional.md +50 -0
- data/docs/bootstrap/14-sanity-checks.md +44 -0
- data/docs/bootstrap/README.md +90 -0
- data/docs/cookie_authentication_guide.md +11 -0
- data/docs/extending.md +3 -3
- data/docs/host_integration_guide.md +3 -281
- data/docs/initializing.md +37 -28
- data/docs/messaging_integration_guide.md +1 -1
- data/docs/principal_capabilities.md +1 -1
- data/docs/upgrades/0.10.0.md +5 -5
- data/docs/upgrades/0.11.0.md +5 -5
- data/docs/upgrades/0.18.0.md +35 -0
- data/docs/upgrades/README.md +2 -1
- data/lib/command_tower/authorization/default.yml +1 -0
- data/lib/command_tower/install/baseline.rb +2 -0
- data/lib/command_tower/version.rb +1 -1
- metadata +25 -2
data/docs/extending.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
How hosts customize and integrate with CommandTower **without forking platform internals**.
|
|
4
4
|
|
|
5
|
-
**New host?** Start with [
|
|
5
|
+
**New host?** Start with [Bootstrap a new host](bootstrap/README.md).
|
|
6
6
|
|
|
7
7
|
Layer map: [architecture.md](architecture.md). Install/configure/migrate/doctor: [initializing.md](initializing.md). Testing API: [testing.md](testing.md). HTTP catalog: [api_reference.md](api_reference.md).
|
|
8
8
|
|
|
@@ -42,11 +42,11 @@ Full install narrative: [initializing.md](initializing.md).
|
|
|
42
42
|
|
|
43
43
|
| Surface | Notes |
|
|
44
44
|
|---------|--------|
|
|
45
|
-
| `
|
|
45
|
+
| `command_tower:install` (`make rails ARGS='command_tower:install'`) | Task name inside the container; migrations + optional configure |
|
|
46
46
|
| `SKIP_CONFIGURE=1` | Migrations only |
|
|
47
47
|
| `SKIP_MOUNT=1` | Initializer without mount |
|
|
48
48
|
| `FORCE=1` | Overwrite initializer |
|
|
49
|
-
| `rails
|
|
49
|
+
| `command_tower:configure` (`make rails ARGS='generate command_tower:configure'`) | `--skip-routes`, `--force` |
|
|
50
50
|
|
|
51
51
|
## Mounting and configuration
|
|
52
52
|
|
|
@@ -1,285 +1,7 @@
|
|
|
1
1
|
# Host Integration Guide
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The new-host manual lives in [`bootstrap/README.md`](bootstrap/README.md).
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
That directory is the bring-up authority: implementation order, Docker-only Make wrappers, one file per step, and an explicit note that the manual is **local bring-up** (not deployment).
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
## Prerequisites
|
|
10
|
-
|
|
11
|
-
- A Rails host application (engine is mounted into the host)
|
|
12
|
-
- `gem "command_tower"` in the host Gemfile (path, git, or released gem)
|
|
13
|
-
- Host-owned database (and any host Redis/job stack your app already uses)
|
|
14
|
-
|
|
15
|
-
## Step 1 — Install
|
|
16
|
-
|
|
17
|
-
Follow [Initializing](initializing.md):
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
bundle install
|
|
21
|
-
bin/rails command_tower:install
|
|
22
|
-
bin/rails db:migrate
|
|
23
|
-
bin/rails command_tower:doctor
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
That copies CT migrations, generates `config/initializers/command_tower.rb` (unless skipped), and mounts `CommandTower::Engine` (unless skipped).
|
|
27
|
-
|
|
28
|
-
## Step 2 — Secrets and initializer
|
|
29
|
-
|
|
30
|
-
In the host initializer, set at least:
|
|
31
|
-
|
|
32
|
-
- `config.jwt.hmac_secret`
|
|
33
|
-
- `config.signup_session.jwt_secret` (or `SIGNUP_SESSION_JWT_SECRET`)
|
|
34
|
-
- `config.password_recovery_session.jwt_secret` (or `PASSWORD_RECOVERY_SESSION_JWT_SECRET`)
|
|
35
|
-
- `config.application.host_key` — server-bound host/product identity for CT-generic user-scoped state (for example experience states). Not client-supplied. Blank values cause Me experience-state routes to return **503** `experience_states_host_unconfigured`.
|
|
36
|
-
|
|
37
|
-
Re-run `bin/rails command_tower:doctor`. Details: [Initializing — Configuration](initializing.md#configuration).
|
|
38
|
-
|
|
39
|
-
Dummy-host reference: [`rails_app/config/initializers/command_tower.rb`](../rails_app/config/initializers/command_tower.rb) (sets `host_key` to `"command_tower"`).
|
|
40
|
-
|
|
41
|
-
### Email / SMTP
|
|
42
|
-
|
|
43
|
-
CommandTower owns ActionMailer delivery for engine mailers (email verification, password reset, messaging channel mail).
|
|
44
|
-
|
|
45
|
-
| Environment | Delivery |
|
|
46
|
-
|-------------|----------|
|
|
47
|
-
| `test` | `:test` (in-memory `ActionMailer::Base.deliveries`; no external SMTP) |
|
|
48
|
-
| development / production | `:smtp` unless the host explicitly sets `config.email.delivery_method` |
|
|
49
|
-
|
|
50
|
-
Non-secret knobs live on `config.email.*` (defaults: `smtp.gmail.com`, port `587`, `plain`, STARTTLS auto). Secrets come from Credential Resolution:
|
|
51
|
-
|
|
52
|
-
- `config.credentials.smtp.user_name` / `password`, or
|
|
53
|
-
- ENV `GMAIL_USER_NAME` / `GMAIL_PASSWORD`
|
|
54
|
-
|
|
55
|
-
`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.
|
|
56
|
-
|
|
57
|
-
Doctor does **not** probe SMTP connectivity.
|
|
58
|
-
|
|
59
|
-
## Step 3 — Confirm mount path
|
|
60
|
-
|
|
61
|
-
Ensure routes include something like:
|
|
62
|
-
|
|
63
|
-
```ruby
|
|
64
|
-
mount CommandTower::Engine => "/" # or "/api"
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
Engine paths below are **relative to that mount**. Route area index: [Controllers](controllers.md).
|
|
68
|
-
|
|
69
|
-
## Step 3b — Inherit execution-boundary bases
|
|
70
|
-
|
|
71
|
-
Greenfield hosts should inherit CommandTower bases so Execution Context is established automatically:
|
|
72
|
-
|
|
73
|
-
```ruby
|
|
74
|
-
class ApplicationController < CommandTower::ApplicationController
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
class ApplicationJob < CommandTower::ApplicationJob
|
|
78
|
-
end
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
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`.
|
|
82
|
-
|
|
83
|
-
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) { ... }`.
|
|
84
|
-
|
|
85
|
-
`Auth::RequestContext` is JWT request/response transport, not Execution Context. Details: [Extending — Execution Context](extending.md#execution-context).
|
|
86
|
-
|
|
87
|
-
Lifecycle and semantic events: [Eventing](eventing.md).
|
|
88
|
-
|
|
89
|
-
If superclass inheritance is technically blocked, include `CommandTower::Execution::HttpBoundary` / `JobBoundary` on the host bases. That is an escape hatch, not the preferred contract.
|
|
90
|
-
|
|
91
|
-
## Step 4 — Host RBAC (required)
|
|
92
|
-
|
|
93
|
-
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.
|
|
94
|
-
|
|
95
|
-
The host YAML (default `config/rbac_groups.yml`) is a **second source**. Composition is additive. Hosts:
|
|
96
|
-
|
|
97
|
-
1. Define product roles (typically `member`).
|
|
98
|
-
2. **Grant names** of already-defined CT entities to those roles.
|
|
99
|
-
3. Optionally define **host-owned** entities for **host** controllers.
|
|
100
|
-
4. Deliberately compose operational Admin roles (least privilege or a broad host-owned `admin`).
|
|
101
|
-
|
|
102
|
-
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).
|
|
103
|
-
|
|
104
|
-
Dummy host grants-only example: [`rails_app/config/rbac_groups.yml`](../rails_app/config/rbac_groups.yml).
|
|
105
|
-
|
|
106
|
-
```ruby
|
|
107
|
-
CommandTower.configure do |c|
|
|
108
|
-
c.authorization.rbac_group_path = Rails.root.join("config/rbac_groups.yml")
|
|
109
|
-
c.authorization.default_membership_role = "member" # optional; nil disables
|
|
110
|
-
end
|
|
111
|
-
```
|
|
112
|
-
|
|
113
|
-
`default_membership_role` is validated against the **composed** graph at boot. Unknown names fail configuration finalization.
|
|
114
|
-
|
|
115
|
-
More: [Authorization](authorization.md), [Authentication & authorization guide](authentication_authorization_guide.md).
|
|
116
|
-
|
|
117
|
-
Product audit names (later) register additively:
|
|
118
|
-
|
|
119
|
-
```ruby
|
|
120
|
-
CommandTower.configure do |c|
|
|
121
|
-
c.registry.audit.event :wager_placed do |event|
|
|
122
|
-
event.allowed_changes = %i[status]
|
|
123
|
-
event.user_history = true
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
Do **not** redefine CommandTower-owned audit names. Audit rows persist in CommandTower after `command_tower:install:migrations` and `db:migrate`. See [Audit](audit.md).
|
|
129
|
-
|
|
130
|
-
Admin Workspace tools register additively (navigation metadata, not a dispatcher):
|
|
131
|
-
|
|
132
|
-
```ruby
|
|
133
|
-
CommandTower.configure do |c|
|
|
134
|
-
c.registry.admin_workspace.tool :host_example do |tool|
|
|
135
|
-
tool.label = "Example"
|
|
136
|
-
tool.description = "Short launcher explanation of what this tool does."
|
|
137
|
-
tool.route = "/admin/example"
|
|
138
|
-
tool.group = :product
|
|
139
|
-
tool.sort_order = 300
|
|
140
|
-
tool.required_entity = :host_example_entity
|
|
141
|
-
end
|
|
142
|
-
end
|
|
143
|
-
```
|
|
144
|
-
|
|
145
|
-
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).
|
|
146
|
-
|
|
147
|
-
Principal capabilities register additively (frontend-projectable ids, not a dump of all entities):
|
|
148
|
-
|
|
149
|
-
```ruby
|
|
150
|
-
CommandTower.configure do |c|
|
|
151
|
-
c.registry.principal_capabilities.capability :host_example do |capability|
|
|
152
|
-
capability.required_entity = :host_example_entity
|
|
153
|
-
end
|
|
154
|
-
end
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
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.
|
|
158
|
-
|
|
159
|
-
## Step 5 — Roles on users
|
|
160
|
-
|
|
161
|
-
RBAC YAML defines what a role **may** do. Users still need that role assigned.
|
|
162
|
-
|
|
163
|
-
- Specs and the dummy pattern use role name **`member`** for Me/Auth surfaces (including entity `principal_capabilities`).
|
|
164
|
-
- 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.
|
|
165
|
-
- When it is `nil`, register does not attach roles. Hosts may assign via product logic, ops (`command_tower:users:create`), or other host workflows.
|
|
166
|
-
- Installing CommandTower does **not** grant operational Admin access. Hosts must deliberately grant Admin entities.
|
|
167
|
-
- Admin announcements need a host role that includes `admin_messaging_announcements`.
|
|
168
|
-
- Admin Workspace manifest needs a host role that includes `admin_workspace`. Tool visibility still depends on each tool's `required_entity`.
|
|
169
|
-
- `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.
|
|
170
|
-
- A host may define a broad `admin` role that grants many Admin entities — that is host policy, not a CommandTower default.
|
|
171
|
-
## Step 6 — Enable feature gates you need
|
|
172
|
-
|
|
173
|
-
When a gate is off, the route is **not drawn** → **404**.
|
|
174
|
-
|
|
175
|
-
| Capability | Config |
|
|
176
|
-
|------------|--------|
|
|
177
|
-
| Plain-text login | `config.login.plain_text.enable = true` |
|
|
178
|
-
| Email verification routes | `config.login.plain_text.email_verify = true` |
|
|
179
|
-
| Password reset routes | `config.login.plain_text.password_reset = true` |
|
|
180
|
-
| Email availability | `config.signup_session.email_availability = true` |
|
|
181
|
-
| Username availability | `config.username.realtime_username_check = true` |
|
|
182
|
-
|
|
183
|
-
Full gate list: [API reference — Feature gates](api_reference.md#feature-gates).
|
|
184
|
-
|
|
185
|
-
## Step 7 — Choose auth client path
|
|
186
|
-
|
|
187
|
-
- **API / mobile:** `Authorization: Bearer <jwt>` (default)
|
|
188
|
-
- **Browser / SPA cookies:** enable `config.jwt.cookie` (+ CSRF as needed) — [Cookie authentication](cookie_authentication_guide.md)
|
|
189
|
-
|
|
190
|
-
Engine HTTP success/error bodies use the application envelope `{ data, meta, errors }`. Host provisional helpers (`authenticate_user!`) can differ — [Authentication](authentication.md).
|
|
191
|
-
|
|
192
|
-
## Step 8 — Smoke check
|
|
193
|
-
|
|
194
|
-
With gates and RBAC in place (paths relative to your mount):
|
|
195
|
-
|
|
196
|
-
1. `POST /auth/register` (always drawn) — with `default_membership_role` configured, the user is created **and** assigned that role in one transaction.
|
|
197
|
-
2. `POST /auth/plain-text/login` (if enabled) — receive `data.token`.
|
|
198
|
-
3. `GET /me` with Bearer token — expect **200** and envelope `data` (account payload).
|
|
199
|
-
|
|
200
|
-
If you get **401**, authn failed. If you get **403**, fix Steps 4–5 before debugging clients.
|
|
201
|
-
|
|
202
|
-
Contracts: [API reference](api_reference.md).
|
|
203
|
-
|
|
204
|
-
## Step 9 — Messaging (when needed)
|
|
205
|
-
|
|
206
|
-
Host owns:
|
|
207
|
-
|
|
208
|
-
- Notification catalog / types
|
|
209
|
-
- `platform_enabled_channels` / channel policy
|
|
210
|
-
- Adapter credentials (email / SMS / Pushover)
|
|
211
|
-
|
|
212
|
-
Emit from a **host product workflow** via `Communications::Produce` / `ProduceMany` — not by bypassing workflows. See [Messaging](messaging_integration_guide.md) and [Extending](extending.md).
|
|
213
|
-
|
|
214
|
-
Phone/Pushover routes are always drawn; missing product readiness returns **503** capability errors.
|
|
215
|
-
|
|
216
|
-
## Step 10 — Host tests
|
|
217
|
-
|
|
218
|
-
```ruby
|
|
219
|
-
require "command_tower/testing"
|
|
220
|
-
CommandTower::Testing.install!
|
|
221
|
-
```
|
|
222
|
-
|
|
223
|
-
Details: [Testing](testing.md). Prefer `spec/requests/` patterns in the gem as HTTP contract proof.
|
|
224
|
-
|
|
225
|
-
## Step 11 — Client version compatibility (optional)
|
|
226
|
-
|
|
227
|
-
CommandTower can gate or warn on stale client app versions per platform, ahead of every request (before authn/authz). It is entirely opt-in: an untouched registry is a no-op, and the shipped CommandTower-owned catalog is empty by design.
|
|
228
|
-
|
|
229
|
-
```ruby
|
|
230
|
-
CommandTower.configure do |c|
|
|
231
|
-
c.registry.client_compatibility.mode = :observe # or :enforce; default observe
|
|
232
|
-
|
|
233
|
-
c.registry.client_compatibility.platform :web do |platform|
|
|
234
|
-
platform.minimum = "1.0.0" # required; MAJOR.MINOR.PATCH[.prerelease]
|
|
235
|
-
platform.recommended = "1.2.0" # optional; non-blocking upgrade guidance
|
|
236
|
-
# platform.update_url is forbidden on :web (web recovery is "reload", not a store)
|
|
237
|
-
end
|
|
238
|
-
|
|
239
|
-
c.registry.client_compatibility.platform :ios do |platform|
|
|
240
|
-
platform.minimum = "2.0.0"
|
|
241
|
-
platform.update_url = "https://apps.apple.com/app/id..."
|
|
242
|
-
end
|
|
243
|
-
|
|
244
|
-
# Optional: a capability-scoped floor stricter than the platform-global minimum,
|
|
245
|
-
# bound to an existing RBAC entity (see Step 4).
|
|
246
|
-
c.registry.client_compatibility.entity :some_host_entity do |entity|
|
|
247
|
-
entity.minimum.ios = "2.1.0"
|
|
248
|
-
end
|
|
249
|
-
end
|
|
250
|
-
```
|
|
251
|
-
|
|
252
|
-
- Canonical platforms are `web` | `ios` | `android`. Configure only what your app ships — `android` unconfigured never fails boot on its own.
|
|
253
|
-
- Zero configured platforms while otherwise opted in (non-default `mode`, any `entity`/`client_contract`/`bind`) fails boot fast; a completely untouched registry does not.
|
|
254
|
-
- `X-App-Version` / `X-Client-Platform` request headers drive evaluation — never User-Agent, never an allowlist.
|
|
255
|
-
- Every controller runs the check via `before_action :evaluate_client_compatibility!` on `CommandTower::ApplicationController`. Exempt a controller with `skip_before_action :evaluate_client_compatibility!`.
|
|
256
|
-
- Process-level ENV overlays may only **raise** an already-configured platform's minimum, never lower it or configure a platform the host didn't enable: `COMMAND_TOWER_CLIENT_COMPATIBILITY_MODE=observe|enforce`, `COMMAND_TOWER_CLIENT_COMPATIBILITY_MINIMUM_WEB|IOS|ANDROID=<version>`.
|
|
257
|
-
- In `:observe` mode nothing is ever blocked — decisions are logged only. In `:enforce` mode, an incompatible or unparseable identity renders HTTP `426` with a `client_update_required` error envelope.
|
|
258
|
-
- Recommended-update guidance (when a client is compatible but below `recommended`) is surfaced only in `meta.clientCompatibility` on login and session-show responses — never on every response.
|
|
259
|
-
|
|
260
|
-
## Done when
|
|
261
|
-
|
|
262
|
-
- Doctor passes for secrets/migrations
|
|
263
|
-
- Host `rbac_groups.yml` maps Me/Auth entities
|
|
264
|
-
- Users who should use Me surfaces have the host `member` (or equivalent) role
|
|
265
|
-
- Needed feature gates are enabled
|
|
266
|
-
- `GET /me` returns **200** with a Bearer token for a member user
|
|
267
|
-
|
|
268
|
-
## Common failures
|
|
269
|
-
|
|
270
|
-
| Symptom | Likely cause |
|
|
271
|
-
|---------|----------------|
|
|
272
|
-
| Doctor green, `GET /me` is **403** | Missing host RBAC YAML or user lacks `member` (Steps 4–5) |
|
|
273
|
-
| `POST /auth/plain-text/login` is **404** | `login.plain_text.enable?` off (Step 6) |
|
|
274
|
-
| Password-reset / availability **404** | Matching feature gate off |
|
|
275
|
-
| Phone / Pushover **503** | Capability / adapters not ready (Step 9) |
|
|
276
|
-
| Confused error JSON on host controllers | Provisional `authenticate_user!` vs engine envelope (Step 7) |
|
|
277
|
-
|
|
278
|
-
## Related
|
|
279
|
-
|
|
280
|
-
- [Initializing](initializing.md)
|
|
281
|
-
- [Extending](extending.md)
|
|
282
|
-
- [API reference](api_reference.md)
|
|
283
|
-
- [Authentication & authorization guide](authentication_authorization_guide.md)
|
|
284
|
-
- [Messaging](messaging_integration_guide.md)
|
|
285
|
-
- [README](../README.md)
|
|
7
|
+
Do not treat this page as a second checklist. Follow the bootstrap README.
|
data/docs/initializing.md
CHANGED
|
@@ -1,31 +1,38 @@
|
|
|
1
1
|
# Initializing CommandTower
|
|
2
2
|
|
|
3
|
-
CommandTower is a Rails engine.
|
|
3
|
+
CommandTower is a Rails engine. This page is the **install / flags / schema / doctor contract**. A new host does not start here — follow [Bootstrap a new host](bootstrap/README.md) (Docker + Make, in order). That alone is not a fully usable Me/Auth host; bootstrap continues through RBAC, feature gates, and `GET /me`.
|
|
4
|
+
|
|
5
|
+
Ruby/Rails runs **inside Compose**. Operator commands below are `make …`. `bin/rails command_tower:install` is the **task name** inside the container, not a host-machine command.
|
|
4
6
|
|
|
5
7
|
Back to [README](../README.md).
|
|
6
8
|
|
|
7
9
|
## Quick start
|
|
8
10
|
|
|
9
|
-
|
|
10
|
-
# Gemfile: gem "command_tower" (or path/git source)
|
|
11
|
-
bundle install
|
|
11
|
+
New hosts: [Bootstrap](bootstrap/README.md) (author Docker/Make first, then `make rails-new`, then pin the gem). After the gem is pinned:
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
```bash
|
|
14
|
+
make rails ARGS='command_tower:install'
|
|
15
|
+
make migrate
|
|
16
|
+
make doctor
|
|
16
17
|
```
|
|
17
18
|
|
|
18
19
|
`command_tower:install` does **not** run `db:migrate`. Installing and migrating stay separate on purpose.
|
|
19
20
|
|
|
21
|
+
If the host owns `/api/healthz` (or any route that must sit **before** the engine mount):
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
SKIP_MOUNT=1 make rails ARGS='command_tower:install'
|
|
25
|
+
```
|
|
26
|
+
|
|
20
27
|
### After install
|
|
21
28
|
|
|
22
|
-
Continue with [
|
|
29
|
+
Continue with [Bootstrap](bootstrap/README.md) from the RBAC / gates / smoke steps:
|
|
23
30
|
|
|
24
|
-
1. Host `rbac_groups.yml` product role that **grants** CT-owned Me/Auth entity names (required — otherwise authenticated Me/Auth calls **403**)
|
|
31
|
+
1. Host `rbac_groups.yml` product role that **grants** CT-owned Me/Auth entity names (required — otherwise authenticated Me/Auth calls **403**) — [`bootstrap/09-rbac.md`](bootstrap/09-rbac.md)
|
|
25
32
|
2. Set `authorization.default_membership_role` (for example `"member"`) or assign roles some other supported way
|
|
26
33
|
3. Enable feature gates you need (login, reset, availability, …)
|
|
27
|
-
4. Smoke-check `GET /me`
|
|
28
|
-
5. Wire messaging catalog/adapters when you emit or use phone/Pushover
|
|
34
|
+
4. Smoke-check `GET /me` — [`bootstrap/12-smoke-check.md`](bootstrap/12-smoke-check.md)
|
|
35
|
+
5. Wire messaging catalog/adapters when you emit or use phone/Pushover — [`bootstrap/13-optional.md`](bootstrap/13-optional.md)
|
|
29
36
|
|
|
30
37
|
### What `command_tower:install` does
|
|
31
38
|
|
|
@@ -41,22 +48,24 @@ Continue with [Host integration](host_integration_guide.md):
|
|
|
41
48
|
| `SKIP_MOUNT=1` | Generate initializer but do not mount routes |
|
|
42
49
|
| `FORCE=1` | Overwrite an existing `config/initializers/command_tower.rb` |
|
|
43
50
|
|
|
44
|
-
Examples:
|
|
51
|
+
Examples (Make; flags enter the container via the `rails` recipe in [`bootstrap/01-docker-make-compose.md`](bootstrap/01-docker-make-compose.md)):
|
|
45
52
|
|
|
46
53
|
```bash
|
|
47
|
-
SKIP_CONFIGURE=1
|
|
48
|
-
SKIP_MOUNT=1
|
|
49
|
-
FORCE=1
|
|
54
|
+
SKIP_CONFIGURE=1 make rails ARGS='command_tower:install'
|
|
55
|
+
SKIP_MOUNT=1 make rails ARGS='command_tower:install'
|
|
56
|
+
FORCE=1 make rails ARGS='command_tower:install'
|
|
50
57
|
```
|
|
51
58
|
|
|
59
|
+
Task names inside the container remain `command_tower:install` (and `command_tower:install:migrations`). Do not run `bin/rails` on the Mac.
|
|
60
|
+
|
|
52
61
|
## Configure generator (optional)
|
|
53
62
|
|
|
54
63
|
Prefer `command_tower:install` for greenfield hosts. The generator remains available:
|
|
55
64
|
|
|
56
65
|
```bash
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
66
|
+
make rails ARGS='generate command_tower:configure'
|
|
67
|
+
make rails ARGS='generate command_tower:configure --skip-routes'
|
|
68
|
+
make rails ARGS='generate command_tower:configure --force'
|
|
60
69
|
```
|
|
61
70
|
|
|
62
71
|
The generator:
|
|
@@ -75,8 +84,8 @@ CommandTower is the **sole authoring authority** for CommandTower-owned schema (
|
|
|
75
84
|
Handled by `command_tower:install` (step 1). Equivalent migration-only command:
|
|
76
85
|
|
|
77
86
|
```bash
|
|
78
|
-
|
|
79
|
-
|
|
87
|
+
make rails ARGS='command_tower:install:migrations'
|
|
88
|
+
make migrate
|
|
80
89
|
```
|
|
81
90
|
|
|
82
91
|
`command_tower:install:migrations` is the standard Rails engine task (wrapper around `railties:install:migrations`). Re-running installation is **idempotent**: already-installed CommandTower migrations are skipped.
|
|
@@ -86,9 +95,9 @@ Rails may **retimestamp** newly copied host files. That is normal. Do not hand-e
|
|
|
86
95
|
### Upgrade workflow
|
|
87
96
|
|
|
88
97
|
1. Bump/release the CommandTower gem dependency in the host.
|
|
89
|
-
2. Run `
|
|
90
|
-
3. Run `
|
|
91
|
-
4. Optionally run `
|
|
98
|
+
2. Run `SKIP_CONFIGURE=1 make rails ARGS='command_tower:install'` (or `make rails ARGS='command_tower:install:migrations'`).
|
|
99
|
+
3. Run `make migrate`.
|
|
100
|
+
4. Optionally run `make doctor`.
|
|
92
101
|
|
|
93
102
|
Do not re-run configure on hosts that already customize the initializer unless you intend to regenerate it (`FORCE=1`).
|
|
94
103
|
|
|
@@ -120,7 +129,7 @@ The generated initializer documents available options via class_composer. Defaul
|
|
|
120
129
|
## Doctor
|
|
121
130
|
|
|
122
131
|
```bash
|
|
123
|
-
|
|
132
|
+
make doctor
|
|
124
133
|
```
|
|
125
134
|
|
|
126
135
|
Checks Rails version compatibility, engine baseline migrations, host-installed migration copies, JWT / session secrets, and messaging adapter names. Failures abort with remediation text; warnings print but allow success.
|
|
@@ -151,12 +160,12 @@ Doctor does **not** probe Redis/SMTP connectivity.
|
|
|
151
160
|
If the host already has a bespoke initializer and mount:
|
|
152
161
|
|
|
153
162
|
```bash
|
|
154
|
-
SKIP_CONFIGURE=1
|
|
155
|
-
# same as:
|
|
156
|
-
|
|
163
|
+
SKIP_CONFIGURE=1 make rails ARGS='command_tower:install'
|
|
164
|
+
# same task as: make rails ARGS='command_tower:install:migrations'
|
|
165
|
+
make migrate
|
|
157
166
|
```
|
|
158
167
|
|
|
159
|
-
Do not run `rails g command_tower:configure` unless you intentionally want stock scaffolding.
|
|
168
|
+
Do not run `rails g command_tower:configure` unless you intentionally want stock scaffolding (`make rails ARGS='generate command_tower:configure'`).
|
|
160
169
|
|
|
161
170
|
## Test suite factories
|
|
162
171
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
CommandTower owns modern messaging **platform** surfaces. Hosts supply product content, channel policy, and adapter credentials.
|
|
4
4
|
|
|
5
|
-
Complete install and host RBAC first: [
|
|
5
|
+
Complete install and host RBAC first: [Bootstrap a new host](bootstrap/README.md) (through [`12-smoke-check.md`](bootstrap/12-smoke-check.md) before emitting or wiring phone/Pushover).
|
|
6
6
|
|
|
7
7
|
## Current surfaces
|
|
8
8
|
|
|
@@ -86,4 +86,4 @@ Me/Auth route gates (`session`, `me`, …) and unimplemented Admin entities are
|
|
|
86
86
|
- [API reference](api_reference.md#get-authprincipal-capabilities)
|
|
87
87
|
- [Authorization](authorization.md)
|
|
88
88
|
- [Admin Workspace](admin_workspace.md) — tool manifest only; not a UI permission probe
|
|
89
|
-
- [
|
|
89
|
+
- [Bootstrap a new host](bootstrap/README.md)
|
data/docs/upgrades/0.10.0.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
**From:** `0.5.0` (and earlier SchemaHelper-era hosts on `main`)
|
|
4
4
|
**To:** `0.10.0`
|
|
5
5
|
|
|
6
|
-
This document summarizes **all changes from `main` through the `0.10.0` release** (committed work on `schematizing` plus the modern platform working tree). It is the durable upgrade / change SST for hosts. For a sequenced blank-host path, use [
|
|
6
|
+
This document summarizes **all changes from `main` through the `0.10.0` release** (committed work on `schematizing` plus the modern platform working tree). It is the durable upgrade / change SST for hosts. For a sequenced blank-host path, use [Bootstrap a new host](../bootstrap/README.md).
|
|
7
7
|
|
|
8
8
|
## Breaking / host-impact
|
|
9
9
|
|
|
@@ -31,15 +31,15 @@ This document summarizes **all changes from `main` through the `0.10.0` release*
|
|
|
31
31
|
## Documentation
|
|
32
32
|
|
|
33
33
|
- Canonical HTTP catalog: [API reference](../api_reference.md)
|
|
34
|
-
- Host start-here: [
|
|
34
|
+
- Host start-here: [Bootstrap a new host](../bootstrap/README.md)
|
|
35
35
|
- Install SST: [Initializing](../initializing.md)
|
|
36
36
|
- Auth/cookie/RBAC guides refreshed to modern routes
|
|
37
37
|
- Messaging and pagination aligned to Inbox + Produce contracts
|
|
38
38
|
|
|
39
39
|
## Upgrade checklist (existing hosts)
|
|
40
40
|
|
|
41
|
-
1. Bump gem to `0.10.0` and run `
|
|
42
|
-
2. Set JWT + signup/recovery session secrets; run `
|
|
41
|
+
1. Bump gem to `0.10.0` and run `make rails ARGS='command_tower:install:migrations'` (or `SKIP_CONFIGURE=1 make rails ARGS='command_tower:install'`) then `make migrate`.
|
|
42
|
+
2. Set JWT + signup/recovery session secrets; run `make doctor`.
|
|
43
43
|
3. Add host `rbac_groups.yml` with a product role (typically `member`) that **grants** CT-owned entity names; do not redefine engine `owner` or copy CT controller mappings. Operational Admin roles are host-owned.
|
|
44
44
|
4. Ensure users who need Me surfaces have the host `member` role (or equivalent).
|
|
45
45
|
5. Enable feature gates you rely on (`login.plain_text.enable`, password reset, email verify, availability).
|
|
@@ -47,7 +47,7 @@ This document summarizes **all changes from `main` through the `0.10.0` release*
|
|
|
47
47
|
7. Smoke: Bearer login → `GET /me` returns **200** (not **403**).
|
|
48
48
|
8. If emitting messages: catalog, channels, adapters; call Produce from host workflows.
|
|
49
49
|
|
|
50
|
-
New hosts: follow [
|
|
50
|
+
New hosts: follow [Bootstrap a new host](../bootstrap/README.md) end-to-end.
|
|
51
51
|
|
|
52
52
|
## Related
|
|
53
53
|
|
data/docs/upgrades/0.11.0.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
**From:** `0.10.0`
|
|
4
4
|
**To:** `0.11.0`
|
|
5
5
|
|
|
6
|
-
This document summarizes **host-visible changes from `0.10.0` through the `0.11.0` release**. It is the durable upgrade / change SST for hosts already on the Auth/Me/Messaging platform. For a sequenced blank-host path, use [
|
|
6
|
+
This document summarizes **host-visible changes from `0.10.0` through the `0.11.0` release**. It is the durable upgrade / change SST for hosts already on the Auth/Me/Messaging platform. For a sequenced blank-host path, use [Bootstrap a new host](../bootstrap/README.md).
|
|
7
7
|
|
|
8
8
|
## Breaking / host-impact
|
|
9
9
|
|
|
@@ -67,13 +67,13 @@ See dummy composition in `rails_app/config/rbac_groups.yml`.
|
|
|
67
67
|
- [Audit](../audit.md)
|
|
68
68
|
- [Admin Workspace](../admin_workspace.md)
|
|
69
69
|
- [Principal capabilities](../principal_capabilities.md)
|
|
70
|
-
- Host start-here: [
|
|
70
|
+
- Host start-here: [Bootstrap a new host](../bootstrap/README.md)
|
|
71
71
|
- Canonical HTTP catalog: [API reference](../api_reference.md)
|
|
72
72
|
|
|
73
73
|
## Upgrade checklist (existing 0.10.0 hosts)
|
|
74
74
|
|
|
75
|
-
1. Bump gem to `0.11.0` and run `
|
|
76
|
-
2. Run `
|
|
75
|
+
1. Bump gem to `0.11.0` and run `make rails ARGS='command_tower:install:migrations'` then `make migrate`.
|
|
76
|
+
2. Run `make doctor`.
|
|
77
77
|
3. Grant `principal_capabilities` (and `me_audit_events` if Account Activity is in product) on host `member` / equivalent.
|
|
78
78
|
4. Explicitly grant Admin entities to **host-owned** operator roles (`admin_workspace`, `admin_users`, `admin_users_update`, `admin_rbac_assignments`, `admin_audit_events`, `admin_impersonation`) — do not assume a platform `admin` role.
|
|
79
79
|
5. Point Admin FE gating at `GET /auth/principal-capabilities`, not `GET /admin/workspace`.
|
|
@@ -81,7 +81,7 @@ See dummy composition in `rails_app/config/rbac_groups.yml`.
|
|
|
81
81
|
7. Smoke: login → `GET /auth/principal-capabilities` **200**; operator with grants → `GET /admin/workspace` **200**; without grants → **403**.
|
|
82
82
|
8. Smoke: start impersonation → Admin routes **418**; `DELETE /auth/impersonation-session` restores actor.
|
|
83
83
|
|
|
84
|
-
New hosts: follow [
|
|
84
|
+
New hosts: follow [Bootstrap a new host](../bootstrap/README.md) end-to-end (includes 0.10.0 foundations plus this release).
|
|
85
85
|
|
|
86
86
|
## Related
|
|
87
87
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# Upgrade: CommandTower 0.18.0
|
|
2
|
+
|
|
3
|
+
**From:** `0.17.0`
|
|
4
|
+
**To:** `0.18.0`
|
|
5
|
+
|
|
6
|
+
Minor release: inbox conversations. A host can stamp a stable key and title when it produces a message. The inbox list groups those messages. A conversation can be read as a window of at most 5 rendered documents. Accept stores the first presentation node's data so a later read does not recompute it. Two new migrations.
|
|
7
|
+
|
|
8
|
+
## Host-visible changes
|
|
9
|
+
|
|
10
|
+
| Change | Host impact |
|
|
11
|
+
|--------|-------------|
|
|
12
|
+
| `Produce` `conversation_key` / `conversation_title` | Optional pair. Both or neither. Stored on the Communication. Not part of the accept fingerprint or metadata. A replay keeps the first pair. A produce with no pair stays null and remains a standalone inbox row. |
|
|
13
|
+
| `GET /me/inbox` | One entry per standalone message or per `conversation_key` in the requested scope. Each entry has `kind` (`message` or `conversation`), `id`, `title`, `preview`, `messageCount`, `unreadCount`, `read`, and `inboxItemIds`. Title and preview come from the latest in-scope member. |
|
|
14
|
+
| `GET /me/inbox/conversation` | Members for `key` and `scope`, oldest first. Does not mark them viewed. `messageCount`, `unreadCount`, and `inboxItemIds` describe the whole scope. `messages` is the rendered slice. |
|
|
15
|
+
| Window params | Optional and mutually exclusive: `around=oldest_unread`, `around=<inbox item id>`, `before`, or `after`, plus `limit`. A window renders at most 5. The first `around` window includes the oldest unread member (or the latest when none are unread), at most 2 older members, then newer members until the total is 5. Omitting the window params returns every member and no `window` key. |
|
|
16
|
+
| Cursor errors | A cursor outside this recipient, key, and scope is `422`. An unknown key stays `404`. `limit` above 5, or combined cursors, is `422`. |
|
|
17
|
+
| `inbox_presentation_snapshot` | Nullable JSON on `messaging_communications`. Accept captures the first presentation node inside the existing transaction. A failed capture stores `{ "v": 1, "omitted": true }`. No presentation node leaves the column null. Inbox detail reads the stored payload. |
|
|
18
|
+
| RBAC | `conversation` is a new action on the existing Me inbox entity. |
|
|
19
|
+
|
|
20
|
+
## Host actions
|
|
21
|
+
|
|
22
|
+
1. Bump gem to `0.18.0` (or `>= 0.18.0`).
|
|
23
|
+
2. Install and migrate:
|
|
24
|
+
- `bundle exec rails command_tower:install:migrations`
|
|
25
|
+
- `bundle exec rails db:migrate`
|
|
26
|
+
- New files: `20261010000001_add_inbox_presentation_snapshot_to_messaging_communications`, `20261010000002_add_conversation_identity_to_messaging_communications`.
|
|
27
|
+
3. Pass `conversation_key` and `conversation_title` from Produce only when a message should join a conversation. Existing producers that omit the pair stay standalone. There is no backfill.
|
|
28
|
+
4. If the host copied the Me inbox action list instead of granting the entity, add `conversation`. An entity grant already includes the new action.
|
|
29
|
+
5. If the host parses `GET /me/inbox` items as one row per inbox item, read the grouped entry shape instead.
|
|
30
|
+
|
|
31
|
+
## Not in this release
|
|
32
|
+
|
|
33
|
+
- No backfill of historical conversation keys or presentation snapshots. Null stays null.
|
|
34
|
+
- No Conversation table. Grouping is the key column plus the list query.
|
|
35
|
+
- CommandTower frontend conversation feed, windowed scrolling, and narrow-web header. Those ship with the frontend package, not this gem.
|
data/docs/upgrades/README.md
CHANGED
|
@@ -4,6 +4,7 @@ Host-facing upgrade / change summaries for CommandTower releases.
|
|
|
4
4
|
|
|
5
5
|
| Version | Summary |
|
|
6
6
|
|---------|---------|
|
|
7
|
+
| [0.18.0](0.18.0.md) | Inbox conversations: optional Produce identity, grouped list entries, `GET /me/inbox/conversation` window of at most 5, accept-time presentation snapshot |
|
|
7
8
|
| [0.17.0](0.17.0.md) | Client-version-compatibility gating (`observe`/`enforce`); Rich Messaging Inbox `content` documents; host-overridable rendering templates; shared email theming |
|
|
8
9
|
| [0.16.0](0.16.0.md) | Me experience-states (`GET`/`POST complete`); `host_key`; `user_experience_states` migration; `me_experience_states` RBAC |
|
|
9
10
|
| [0.15.0](0.15.0.md) | Me `POST /me/push/test` → Produce (`push_delivery_test`); configurable Expo self-test rate limit composers; `me_push#test` |
|
|
@@ -15,4 +16,4 @@ Host-facing upgrade / change summaries for CommandTower releases.
|
|
|
15
16
|
| [0.11.0](0.11.0.md) | Execution context, audit ledger, Admin Workspace, principal capabilities, impersonation, Admin Users |
|
|
16
17
|
| [0.10.0](0.10.0.md) | Modern Auth/Me/Messaging platform; SchemaHelper removal; host RBAC required |
|
|
17
18
|
|
|
18
|
-
New hosts should start with [
|
|
19
|
+
New hosts should start with [Bootstrap a new host](../bootstrap/README.md).
|
|
@@ -16,6 +16,8 @@ module CommandTower
|
|
|
16
16
|
20260817000003_create_command_tower_impersonation_sessions.rb
|
|
17
17
|
20260826140000_add_deleted_at_to_users.rb
|
|
18
18
|
20260906180000_create_user_experience_states.rb
|
|
19
|
+
20261010000001_add_inbox_presentation_snapshot_to_messaging_communications.rb
|
|
20
|
+
20261010000002_add_conversation_identity_to_messaging_communications.rb
|
|
19
21
|
].freeze
|
|
20
22
|
|
|
21
23
|
module_function
|