command_tower 0.16.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.
Files changed (92) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +8 -8
  3. data/app/controllers/command_tower/application_controller.rb +1 -0
  4. data/app/controllers/command_tower/me/inbox_controller.rb +16 -0
  5. data/app/controllers/concerns/command_tower/execution/client_compatibility_boundary.rb +39 -0
  6. data/app/deserializers/command_tower/deserializers/messaging/inbox.rb +85 -0
  7. data/app/errors/command_tower/errors/client_update_required_error.rb +27 -0
  8. data/app/jobs/command_tower/messaging/communications/produce_recipient_job.rb +2 -0
  9. data/app/models/command_tower/messaging/communication.rb +31 -0
  10. data/app/serializers/command_tower/serializers/messaging/inbox.rb +45 -1
  11. data/app/services/command_tower/email_theme/resolver.rb +45 -0
  12. data/app/services/command_tower/messaging/accept/coordinator.rb +16 -1
  13. data/app/services/command_tower/messaging/accept/persister.rb +13 -0
  14. data/app/services/command_tower/messaging/contract/mappers/communication_mapper.rb +7 -0
  15. data/app/services/command_tower/messaging/contract/results/communication_result.rb +1 -0
  16. data/app/services/command_tower/messaging/inbox/conversation_result.rb +31 -0
  17. data/app/services/command_tower/messaging/inbox/entry_result.rb +36 -0
  18. data/app/services/command_tower/messaging/inbox/reader.rb +231 -18
  19. data/app/services/command_tower/messaging/inbox.rb +4 -0
  20. data/app/services/command_tower/messaging/notification_types/declaration.rb +14 -1
  21. data/app/services/command_tower/messaging/rendering/channel_renderer.rb +44 -13
  22. data/app/services/command_tower/messaging/rendering/inbox_document.rb +131 -0
  23. data/app/services/command_tower/messaging/rendering/inbox_document_renderer.rb +224 -0
  24. data/app/services/command_tower/messaging/rendering/inbox_presentation_resolver.rb +52 -0
  25. data/app/services/command_tower/messaging/rendering/inbox_presentation_snapshot.rb +64 -0
  26. data/app/services/command_tower/messaging/rendering/template_resolver.rb +128 -0
  27. data/app/services/command_tower/messaging.rb +5 -1
  28. data/app/services/command_tower/services/client_compatibility/evaluate.rb +219 -0
  29. data/app/services/command_tower/services/messaging/communications/produce.rb +4 -0
  30. data/app/services/command_tower/services/messaging/communications/produce_many.rb +6 -0
  31. data/app/services/command_tower/services/messaging/inbox.rb +55 -2
  32. data/app/views/command_tower/email_verification_mailer/verify_email.html.erb +18 -17
  33. data/app/views/command_tower/messaging/rendering/email.html.erb +6 -5
  34. data/app/views/command_tower/password_reset_mailer/reset_password.html.erb +24 -23
  35. data/app/workflows/command_tower/workflows/auth/plain_text/login_workflow.rb +3 -0
  36. data/app/workflows/command_tower/workflows/auth/session/show_workflow.rb +3 -0
  37. data/app/workflows/command_tower/workflows/client_compatibility/evaluate_workflow.rb +83 -0
  38. data/app/workflows/command_tower/workflows/client_compatibility/recommendation_meta.rb +25 -0
  39. data/app/workflows/command_tower/workflows/messaging/communications/produce_recipient_workflow.rb +5 -1
  40. data/app/workflows/command_tower/workflows/messaging/inbox.rb +15 -1
  41. data/config/routes.rb +1 -0
  42. data/db/migrate/20261010000001_add_inbox_presentation_snapshot_to_messaging_communications.rb +7 -0
  43. data/db/migrate/20261010000002_add_conversation_identity_to_messaging_communications.rb +10 -0
  44. data/docs/api_reference.md +13 -2
  45. data/docs/authorization.md +1 -1
  46. data/docs/bootstrap/00-ownership.md +77 -0
  47. data/docs/bootstrap/01-docker-make-compose.md +267 -0
  48. data/docs/bootstrap/02-create-the-rails-app.md +75 -0
  49. data/docs/bootstrap/03-pin-the-gem.md +53 -0
  50. data/docs/bootstrap/04-secrets-and-env.md +59 -0
  51. data/docs/bootstrap/05-install-migrate-doctor.md +74 -0
  52. data/docs/bootstrap/06-mount-and-health.md +53 -0
  53. data/docs/bootstrap/07-execution-bases.md +38 -0
  54. data/docs/bootstrap/08-initializer.md +90 -0
  55. data/docs/bootstrap/09-rbac.md +83 -0
  56. data/docs/bootstrap/10-roles-and-gates.md +62 -0
  57. data/docs/bootstrap/11-auth-client-path.md +57 -0
  58. data/docs/bootstrap/12-smoke-check.md +81 -0
  59. data/docs/bootstrap/13-optional.md +50 -0
  60. data/docs/bootstrap/14-sanity-checks.md +44 -0
  61. data/docs/bootstrap/README.md +90 -0
  62. data/docs/cookie_authentication_guide.md +11 -0
  63. data/docs/extending.md +5 -4
  64. data/docs/host_integration_guide.md +3 -246
  65. data/docs/initializing.md +37 -28
  66. data/docs/messaging_integration_guide.md +39 -1
  67. data/docs/principal_capabilities.md +1 -1
  68. data/docs/upgrades/0.10.0.md +5 -5
  69. data/docs/upgrades/0.11.0.md +5 -5
  70. data/docs/upgrades/0.17.0.md +33 -0
  71. data/docs/upgrades/0.18.0.md +35 -0
  72. data/docs/upgrades/README.md +3 -1
  73. data/lib/command_tower/authorization/default.yml +1 -0
  74. data/lib/command_tower/client_compatibility/version.rb +45 -0
  75. data/lib/command_tower/client_compatibility.rb +23 -0
  76. data/lib/command_tower/configuration/config.rb +6 -0
  77. data/lib/command_tower/configuration/email_theme/config.rb +69 -0
  78. data/lib/command_tower/configuration/registry/client_compatibility/binding_definition.rb +42 -0
  79. data/lib/command_tower/configuration/registry/client_compatibility/config.rb +353 -0
  80. data/lib/command_tower/configuration/registry/client_compatibility/contract_definition.rb +16 -0
  81. data/lib/command_tower/configuration/registry/client_compatibility/entity_requirement_definition.rb +67 -0
  82. data/lib/command_tower/configuration/registry/client_compatibility/minimum_overrides.rb +46 -0
  83. data/lib/command_tower/configuration/registry/client_compatibility/platform_definition.rb +66 -0
  84. data/lib/command_tower/configuration/registry/config.rb +14 -0
  85. data/lib/command_tower/configuration/registry/inbox_presentations/config.rb +105 -0
  86. data/lib/command_tower/configuration/registry/inbox_presentations/presentation_definition.rb +71 -0
  87. data/lib/command_tower/current.rb +1 -0
  88. data/lib/command_tower/engine.rb +4 -0
  89. data/lib/command_tower/inbox_presentations.rb +19 -0
  90. data/lib/command_tower/install/baseline.rb +2 -0
  91. data/lib/command_tower/version.rb +1 -1
  92. metadata +47 -2
@@ -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: [Host integration](host_integration_guide.md) (Steps 1–8 before emitting or wiring phone/Pushover).
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
 
@@ -59,6 +59,44 @@ CommandTower::Services::Messaging::Communications::Produce.call(
59
59
 
60
60
  Admin announcements HTTP is a product path over ProduceMany (async/sync, audience selection). Contract: [API reference — Admin messaging](api_reference.md#admin-messaging).
61
61
 
62
+ ## Rendering template overrides
63
+
64
+ `ChannelRenderer` resolves each rendered destination (email HTML/text, SMS, Pushover, push) by `notification_type_key` first, falling back to a generic template — and always prefers a **host** view over CommandTower's own engine default for either. Hosts customize by dropping ERB files at this path in their own `app/views/`; they never call `ChannelRenderer` or its internal `TemplateResolver` collaborator directly.
65
+
66
+ ```text
67
+ app/views/command_tower/messaging/rendering/
68
+ email.html.erb # optional host override of the generic chrome
69
+ email.text.erb
70
+ sms.text.erb
71
+ push.text.erb
72
+ pushover.text.erb
73
+ <notification_type_key>/
74
+ email.html.erb # optional type-specific override (only if the key matches [a-z0-9_]+)
75
+ email.text.erb
76
+ sms.text.erb
77
+ push.text.erb
78
+ pushover.text.erb
79
+ ```
80
+
81
+ Notes:
82
+
83
+ - `<notification_type_key>` directories only resolve when the key matches `/\A[a-z0-9_]+\z/`. Keys with dots (e.g. legacy `"example.type"`-style keys) or other characters always fall back to generic — they never attempt a type directory, even if one happens to exist on disk.
84
+ - Each rendered destination resolves independently — a type directory can override just `email.html.erb` while every other destination (email text, SMS, Pushover, push) still renders from the generic templates.
85
+ - Generic templates receive the same four locals as before (`title`, `body`, `deep_link`, `h` — an HTML-escaping helper). Type-specific templates additionally receive `metadata` (the communication's metadata Hash) and `notification_type_key`.
86
+ - A missing or failing template (generic or type-specific) surfaces the same way it always has: `RenderError` with code `"render_failed"`.
87
+
88
+ ### Inbox document override (`inbox_document.json.erb`)
89
+
90
+ The Me Inbox detail `content` field (`inbox_document_v1`, see [api_reference.md](api_reference.md#me-inbox)) is built by a separate collaborator, `InboxDocumentRenderer`, using the **same** type-directory convention and sanitized-key rule as above, resolved through `TemplateResolver.render_type_template`:
91
+
92
+ ```text
93
+ app/views/command_tower/messaging/rendering/
94
+ <notification_type_key>/
95
+ inbox_document.json.erb # optional type-specific inbox content override
96
+ ```
97
+
98
+ This override has **no generic ERB fallback file** — the generic Inbox document is built in pure Ruby from `communication.body`/`metadata`, not from a template. Because of that, the fail-open contract here is stricter than `ChannelRenderer`'s: a missing type template, malformed JSON, an envelope with the wrong `schema` or a non-Array `blocks`, or a template that raises mid-render all fall back silently to the generic document — the Inbox read path never surfaces a `RenderError` and never 500s. A valid envelope with one invalid/unknown block strips only that block, keeping the rest; if stripping empties `blocks`, the generic document is used instead.
99
+
62
100
  ## Me Inbox HTTP (summary)
63
101
 
64
102
  | Concern | Contract |
@@ -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
- - [Host integration](host_integration_guide.md)
89
+ - [Bootstrap a new host](bootstrap/README.md)
@@ -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 [Host integration](../host_integration_guide.md).
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: [Host integration](../host_integration_guide.md)
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 `bin/rails command_tower:install:migrations` (or `SKIP_CONFIGURE=1 bin/rails command_tower:install`) then `bin/rails db:migrate`.
42
- 2. Set JWT + signup/recovery session secrets; run `bin/rails command_tower:doctor`.
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 [Host integration](../host_integration_guide.md) end-to-end.
50
+ New hosts: follow [Bootstrap a new host](../bootstrap/README.md) end-to-end.
51
51
 
52
52
  ## Related
53
53
 
@@ -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 [Host integration](../host_integration_guide.md).
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: [Host integration](../host_integration_guide.md)
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 `bin/rails command_tower:install:migrations` then `bin/rails db:migrate`.
76
- 2. Run `bin/rails command_tower:doctor`.
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 [Host integration](../host_integration_guide.md) end-to-end (includes 0.10.0 foundations plus this release).
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,33 @@
1
+ # Upgrade: CommandTower 0.17.0
2
+
3
+ **From:** `0.16.0`
4
+ **To:** `0.17.0`
5
+
6
+ Minor release: client-version-compatibility gating, Rich Messaging Inbox content documents, and shared email theming. No new migration.
7
+
8
+ ## Host-visible changes
9
+
10
+ | Change | Host impact |
11
+ |--------|-------------|
12
+ | `config.registry.client_compatibility` | Optional per-platform (`web`/`ios`/`android`) minimum/recommended version gating, evaluated on every request ahead of authn/authz via `before_action :evaluate_client_compatibility!`. Untouched registry is a no-op. |
13
+ | `:observe` / `:enforce` modes | `:observe` (default) only logs; `:enforce` renders `426` `client_update_required` for incompatible/unparseable client identity |
14
+ | `meta.clientCompatibility` | Non-blocking upgrade guidance surfaced only on login and session-show responses when a compatible client is below `recommended` |
15
+ | `X-App-Version` / `X-Client-Platform` headers | New request contract driving evaluation — never User-Agent |
16
+ | `GET`/`POST open` `/me/inbox/:id` `content` field | New `inbox_document_v1` field rendered at read time from the `Communication` (never persisted); generic Ruby-built `paragraph`/`cta` blocks, or a host `inbox_document.json.erb` override per `notification_type_key` |
17
+ | `Messaging::Rendering::ChannelRenderer` template resolution | Now resolves per-destination host ERB overrides (`app/views/command_tower/messaging/rendering/**`) before falling back to engine defaults, instead of a single fixed `TEMPLATE_DIR` |
18
+ | `config.email_theme` | New composer for semantic email theme tokens; verification and password-reset mailer views now render through `CommandTower::EmailTheme::Resolver` instead of hardcoded hex colors |
19
+ | `config.registry.inbox_presentations` | New host-owned-only registry (no CommandTower-seeded presentations) supporting Rich Messaging Inbox host customization |
20
+
21
+ ## Host actions
22
+
23
+ 1. Bump gem to `0.17.0` (or `>= 0.17.0`).
24
+ 2. No new migration — **`bundle exec rails command_tower:install:migrations` is not required** for this release.
25
+ 3. Optional: configure `config.registry.client_compatibility` per platform if you want version gating; leaving it untouched is a no-op.
26
+ 4. Optional: customize email theme via `config.email_theme`, or override rendered Messaging templates / `inbox_document.json.erb` per `notification_type_key` under `app/views/command_tower/messaging/rendering/`.
27
+ 5. If your host renders `/me/inbox/:id`, expect the new `content` field in detail responses (list items are unaffected).
28
+
29
+ ## Not in this release
30
+
31
+ - CommandTower frontend client-compatibility banners / upgrade prompts
32
+ - CommandTower-owned Inbox presentation catalog entries (registry remains host-owned-only)
33
+ - `android` platform enforcement examples beyond the documented ENV overlay contract
@@ -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.
@@ -4,6 +4,8 @@ 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 |
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 |
7
9
  | [0.16.0](0.16.0.md) | Me experience-states (`GET`/`POST complete`); `host_key`; `user_experience_states` migration; `me_experience_states` RBAC |
8
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` |
9
11
  | [0.14.0](0.14.0.md) | Expo push Messaging channel (`config.messaging.expo`) + `/api/me/push*` registration HTTP; `me_push` RBAC |
@@ -14,4 +16,4 @@ Host-facing upgrade / change summaries for CommandTower releases.
14
16
  | [0.11.0](0.11.0.md) | Execution context, audit ledger, Admin Workspace, principal capabilities, impersonation, Admin Users |
15
17
  | [0.10.0](0.10.0.md) | Modern Auth/Me/Messaging platform; SchemaHelper removal; host RBAC required |
16
18
 
17
- New hosts should start with [Host integration](../host_integration_guide.md).
19
+ New hosts should start with [Bootstrap a new host](../bootstrap/README.md).
@@ -33,6 +33,7 @@ entities:
33
33
  only:
34
34
  - index
35
35
  - show
36
+ - conversation
36
37
  - open
37
38
  - archive
38
39
  - destroy
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module ClientCompatibility
5
+ # Strict client-version identity parsing (authority §12).
6
+ #
7
+ # `Gem::Version` alone is too permissive: it accepts partial shapes such as
8
+ # "1" or "1.2". The authority requires the full MAJOR.MINOR.PATCH shape,
9
+ # with an optional dot-delimited prerelease tail (e.g. "1.2.3.beta.1").
10
+ # Build metadata (e.g. "+build"), hyphenated prerelease tokens, and any
11
+ # other malformed shape are rejected as invalid identity — never coerced
12
+ # into a "valid but low" version.
13
+ module Version
14
+ FORMAT = /\A[vV]?\d+\.\d+\.\d+(\.[A-Za-z0-9]+)*\z/
15
+
16
+ module_function
17
+
18
+ # Returns a normalized (leading "v"/"V" stripped) version string, or nil
19
+ # when the raw value does not conform to the strict MAJOR.MINOR.PATCH
20
+ # (+ optional prerelease) shape.
21
+ def normalize(raw)
22
+ token = raw.to_s.strip
23
+ return nil if token.empty?
24
+ return nil unless token.match?(FORMAT)
25
+
26
+ token.sub(/\A[vV]/, "")
27
+ end
28
+
29
+ def valid?(raw)
30
+ !normalize(raw).nil?
31
+ end
32
+
33
+ # Returns a Gem::Version for a strictly-valid raw value, or nil otherwise.
34
+ # Callers that need a hard failure on invalid input should check
35
+ # `valid?`/`normalize` explicitly rather than relying on Gem::Version's
36
+ # own (looser) parsing.
37
+ def parse(raw)
38
+ normalized = normalize(raw)
39
+ return nil if normalized.nil?
40
+
41
+ Gem::Version.new(normalized)
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CommandTower
4
+ module ClientCompatibility
5
+ class Error < CommandTower::Error; end
6
+
7
+ class HostOverrideError < Error; end
8
+ class DuplicateContractError < Error; end
9
+ class DuplicateEntityRequirementError < Error; end
10
+ class DuplicateBindingError < Error; end
11
+ class UnknownClientContractError < Error; end
12
+ class UnboundClientContractError < Error; end
13
+ class UnknownEntityError < Error; end
14
+ class InvalidPlatformError < Error; end
15
+ class InvalidContractIdError < Error; end
16
+ class InvalidEntityNameError < Error; end
17
+ class InvalidVersionError < Error; end
18
+ class InvalidModeError < Error; end
19
+ class ConflictingRequirementError < Error; end
20
+ class NoConfiguredPlatformsError < Error; end
21
+ class FrozenRegistryError < Error; end
22
+ end
23
+ end
@@ -10,6 +10,7 @@ require "command_tower/configuration/authorization/config"
10
10
  require "command_tower/configuration/base"
11
11
  require "command_tower/configuration/credentials/config"
12
12
  require "command_tower/configuration/email/config"
13
+ require "command_tower/configuration/email_theme/config"
13
14
  require "command_tower/configuration/identity/config"
14
15
  require "command_tower/configuration/impersonation/config"
15
16
  require "command_tower/configuration/jwt/config"
@@ -49,6 +50,11 @@ module CommandTower
49
50
  allowed: Configuration::Email::Config,
50
51
  default: Configuration::Email::Config.new
51
52
 
53
+ add_composer :email_theme,
54
+ desc: "Semantic email theme tokens shared by Messaging and (later) auth mail templates",
55
+ allowed: Configuration::EmailTheme::Config,
56
+ default: Configuration::EmailTheme::Config.new
57
+
52
58
  add_composer :credentials,
53
59
  desc: "Deployment provider credentials (typed per provider under config.credentials.<provider>). Consumed by Credential Resolution. Not provider behavior configuration.",
54
60
  allowed: Configuration::Credentials::Config,
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "class_composer"
4
+
5
+ module CommandTower
6
+ module Configuration
7
+ module EmailTheme
8
+ # Semantic email theme tokens shared by Messaging email templates and,
9
+ # later, CT-owned auth mail templates. CT-owned, gem-level contract —
10
+ # see Rich Messaging Strategy §12 (email theme sequence, step A).
11
+ #
12
+ # Deliberately holds only visual/color tokens. Product identity
13
+ # (name/URL) already exists on Configuration::Application::Config and
14
+ # is composed in by CommandTower::EmailTheme::Resolver, not duplicated
15
+ # here. Defaults are the current unbranded hex values already
16
+ # hardcoded in command_tower's generic email.html.erb and Pick'em's
17
+ # incomplete_wager/email.html.erb, so introducing this contract is a
18
+ # no-op until a template is switched to consume it.
19
+ class Config < ::CommandTower::Configuration::Base
20
+ include ClassComposer::Generator
21
+
22
+ add_composer :canvas_background,
23
+ allowed: String,
24
+ default: "#f4f5f7",
25
+ desc: "Outer email canvas background color"
26
+
27
+ add_composer :surface_background,
28
+ allowed: String,
29
+ default: "#ffffff",
30
+ desc: "Card/surface background color"
31
+
32
+ add_composer :surface_border,
33
+ allowed: String,
34
+ default: "#e2e8f0",
35
+ desc: "Card/surface border color"
36
+
37
+ add_composer :primary_text,
38
+ allowed: String,
39
+ default: "#1a202c",
40
+ desc: "Headline/primary text color"
41
+
42
+ add_composer :body_text,
43
+ allowed: String,
44
+ default: "#4a5568",
45
+ desc: "Body copy text color"
46
+
47
+ add_composer :muted_text,
48
+ allowed: String,
49
+ default: "#718096",
50
+ desc: "De-emphasized/secondary text color"
51
+
52
+ add_composer :accent,
53
+ allowed: String,
54
+ default: "#2b6cb0",
55
+ desc: "Single flat accent color (collapses any gradient to one value)"
56
+
57
+ add_composer :text_on_accent,
58
+ allowed: String,
59
+ default: "#ffffff",
60
+ desc: "Text/icon color rendered on top of the accent color"
61
+
62
+ add_composer :primary_action,
63
+ allowed: String,
64
+ default: "#2b6cb0",
65
+ desc: "Link/button primary-action color"
66
+ end
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "class_composer"
4
+
5
+ module CommandTower
6
+ module Configuration
7
+ module Registry
8
+ module ClientCompatibility
9
+ # Host-owned per-platform host-app version bound to a named client
10
+ # contract. Authority §7.
11
+ class BindingDefinition
12
+ include ClassComposer::Generator
13
+
14
+ add_composer :web, desc: "Bound host-app version for web", allowed: [String, NilClass], default: nil
15
+ add_composer :ios, desc: "Bound host-app version for ios", allowed: [String, NilClass], default: nil
16
+ add_composer :android, desc: "Bound host-app version for android", allowed: [String, NilClass], default: nil
17
+
18
+ def for_platform(platform)
19
+ public_send(platform)
20
+ end
21
+
22
+ def validate!(contract_id:)
23
+ %i[web ios android].each do |platform|
24
+ value = public_send(platform)
25
+ next if value.blank?
26
+
27
+ normalized = CommandTower::ClientCompatibility::Version.normalize(value)
28
+ if normalized.nil?
29
+ raise CommandTower::ClientCompatibility::InvalidVersionError,
30
+ "binding for client contract #{contract_id} has invalid #{platform} #{value.inspect}"
31
+ end
32
+
33
+ public_send("#{platform}=", normalized)
34
+ end
35
+
36
+ self
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end