plutonium 0.62.2 → 0.64.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/.claude/skills/plutonium/SKILL.md +44 -0
- data/.claude/skills/plutonium-app/SKILL.md +3 -3
- data/.claude/skills/plutonium-async-interactions/SKILL.md +191 -0
- data/.claude/skills/plutonium-auth/SKILL.md +36 -0
- data/.claude/skills/plutonium-behavior/SKILL.md +121 -24
- data/.claude/skills/plutonium-kanban/SKILL.md +17 -3
- data/.claude/skills/plutonium-resource/SKILL.md +259 -12
- data/.claude/skills/plutonium-tenancy/SKILL.md +32 -3
- data/.claude/skills/plutonium-ui/SKILL.md +115 -14
- data/.claude/skills/plutonium-wizard/SKILL.md +73 -4
- data/.standard.yml +1 -1
- data/CHANGELOG.md +73 -0
- data/CLAUDE.md +87 -0
- data/Rakefile +34 -0
- data/SECURITY.md +1 -1
- data/app/assets/plutonium.css +1 -1
- data/app/assets/plutonium.js +685 -102
- data/app/assets/plutonium.js.map +4 -4
- data/app/assets/plutonium.min.js +53 -53
- data/app/assets/plutonium.min.js.map +4 -4
- data/app/views/rodauth/_login_form.html.erb +13 -0
- data/db/migrate/async_interactions/20260817000001_create_plutonium_async_runs.rb +170 -0
- data/docs/.vitepress/config.ts +81 -3
- data/docs/.vitepress/theme/blog.data.ts +44 -0
- data/docs/.vitepress/theme/components/BlogIndex.vue +87 -0
- data/docs/.vitepress/theme/components/BlogMeta.vue +47 -0
- data/docs/.vitepress/theme/components/HomeFeatureTour.vue +293 -0
- data/docs/.vitepress/theme/components/HomeHero.vue +3 -3
- data/docs/.vitepress/theme/components/HomeInTheBox.vue +8 -0
- data/docs/.vitepress/theme/components/HomeStopWriting.vue +1 -0
- data/docs/.vitepress/theme/components/HomeWhyPlutonium.vue +84 -0
- data/docs/.vitepress/theme/index.ts +8 -4
- data/docs/blog/association-inputs-post-signed-ids.md +70 -0
- data/docs/blog/fix-the-model-not-the-policy.md +122 -0
- data/docs/blog/fractional-ordering-runs-out-of-room.md +67 -0
- data/docs/blog/half-finished-forms-are-pii.md +76 -0
- data/docs/blog/index.md +13 -0
- data/docs/blog/interactions-are-presentation-objects.md +152 -0
- data/docs/blog/introducing-plutonium.md +253 -0
- data/docs/blog/jobs-are-not-permission-snapshots.md +100 -0
- data/docs/blog/plutonium-and-ai-agents.md +48 -0
- data/docs/blog/realtime-is-one-line-and-four-dependencies.md +70 -0
- data/docs/blog/two-forms-one-dom-id.md +69 -0
- data/docs/blog/whats-new-async-kanban-wizards.md +130 -0
- data/docs/getting-started/tutorial/04-authorization.md +12 -3
- data/docs/getting-started/tutorial/06-nested-resources.md +3 -1
- data/docs/getting-started/tutorial/07-author-portal.md +2 -2
- data/docs/guides/authentication.md +73 -0
- data/docs/guides/authorization.md +2 -0
- data/docs/guides/creating-packages.md +5 -3
- data/docs/guides/custom-actions.md +74 -12
- data/docs/guides/customizing-ui.md +9 -2
- data/docs/guides/index.md +1 -0
- data/docs/guides/kanban.md +7 -5
- data/docs/guides/nested-resources.md +11 -1
- data/docs/guides/performance.md +104 -0
- data/docs/guides/user-invites.md +1 -1
- data/docs/guides/wizards.md +9 -1
- data/docs/index.md +3 -3
- data/docs/public/images/home/tour-actions.png +0 -0
- data/docs/public/images/home/tour-async.png +0 -0
- data/docs/public/images/home/tour-kanban.png +0 -0
- data/docs/public/images/home/tour-tenancy.png +0 -0
- data/docs/public/images/home/tour-wizard.png +0 -0
- data/docs/public/images/reference/async-progress-page.png +0 -0
- data/docs/public/images/reference/async-running-banner.png +0 -0
- data/docs/public/templates/experimental.rb +34 -0
- data/docs/public/templates/pluton8.rb +14 -0
- data/docs/reference/app/portals.md +15 -3
- data/docs/reference/auth/accounts.md +19 -0
- data/docs/reference/behavior/async-interactions.md +295 -0
- data/docs/reference/behavior/controllers.md +17 -4
- data/docs/reference/behavior/index.md +7 -1
- data/docs/reference/behavior/interactions.md +152 -22
- data/docs/reference/configuration.md +5 -0
- data/docs/reference/index.md +1 -0
- data/docs/reference/kanban/dsl.md +7 -4
- data/docs/reference/kanban/index.md +1 -1
- data/docs/reference/kanban/positioning.md +26 -4
- data/docs/reference/positioning.md +568 -0
- data/docs/reference/resource/actions.md +97 -4
- data/docs/reference/resource/definition.md +181 -9
- data/docs/reference/tenancy/invites.md +1 -1
- data/docs/reference/tenancy/nested-resources.md +60 -2
- data/docs/reference/ui/assets.md +4 -0
- data/docs/reference/ui/components.md +57 -4
- data/docs/reference/ui/displays.md +20 -10
- data/docs/reference/ui/index.md +1 -1
- data/docs/reference/wizard/dsl.md +33 -0
- data/docs/reference/wizard/storage-config.md +1 -0
- data/docs/superpowers/plans/2026-07-16-homepage-depth-upgrade.md +624 -0
- data/docs/superpowers/plans/2026-07-16-homepage-depth-upgrade.md.tasks.json +32 -0
- data/docs/superpowers/plans/2026-07-31-positioned-drag-and-drop.md +1787 -0
- data/docs/superpowers/plans/2026-07-31-positioned-drag-and-drop.md.tasks.json +91 -0
- data/docs/superpowers/plans/2026-08-17-async-interactions.md +1414 -0
- data/docs/superpowers/plans/2026-08-17-async-interactions.md.tasks.json +66 -0
- data/docs/superpowers/specs/2026-07-16-homepage-depth-upgrade-design.md +111 -0
- data/docs/superpowers/specs/2026-07-17-action-html-attributes-design.md +124 -0
- data/docs/superpowers/specs/2026-07-31-positioned-drag-and-drop-design.md +506 -0
- data/docs/superpowers/specs/2026-08-17-async-interactions-design.md +185 -0
- data/gemfiles/postgres.gemfile.lock +85 -85
- data/gemfiles/rails_7.gemfile.lock +323 -141
- data/gemfiles/rails_8.0.gemfile.lock +126 -116
- data/gemfiles/rails_8.1.gemfile.lock +127 -117
- data/lib/generators/pu/async_interactions/install_generator.rb +111 -0
- data/lib/generators/pu/async_interactions/templates/app/controllers/async_runs_controller.rb.tt +15 -0
- data/lib/generators/pu/core/typespec/typespec_generator.rb +7 -4
- data/lib/generators/pu/invites/install_generator.rb +3 -3
- data/lib/generators/pu/invites/templates/packages/invites/app/views/layouts/invites/invitation.html.erb.tt +2 -2
- data/lib/generators/pu/lib/plutonium_generators/concerns/mounts_engines.rb +47 -2
- data/lib/generators/pu/lib/plutonium_generators/concerns/resource_registration.rb +41 -0
- data/lib/generators/pu/lite/litestream/litestream_generator.rb +1 -1
- data/lib/generators/pu/lite/solid_queue/solid_queue_generator.rb +1 -1
- data/lib/generators/pu/res/conn/conn_generator.rb +19 -39
- data/lib/generators/pu/res/conn/templates/app/controllers/resource_controller.rb.tt +4 -0
- data/lib/generators/pu/rodauth/templates/app/rodauth/account_rodauth_plugin.rb.tt +15 -6
- data/lib/generators/pu/rodauth/templates/app/rodauth/rodauth_plugin.rb.tt +7 -0
- data/lib/generators/pu/saas/welcome/templates/app/views/layouts/welcome.html.erb.tt +2 -2
- data/lib/generators/pu/wizards/install_generator.rb +78 -0
- data/lib/plutonium/action/base.rb +71 -9
- data/lib/plutonium/action/interactive.rb +9 -0
- data/lib/plutonium/attachments.rb +254 -0
- data/lib/plutonium/configuration.rb +82 -1
- data/lib/plutonium/core/controller.rb +50 -7
- data/lib/plutonium/core/controllers/authorizable.rb +16 -0
- data/lib/plutonium/core/controllers/entity_scoping.rb +12 -2
- data/lib/plutonium/definition/base.rb +51 -0
- data/lib/plutonium/definition/display_layout.rb +112 -0
- data/lib/plutonium/definition/index_views.rb +8 -7
- data/lib/plutonium/definition/input_aliases.rb +38 -0
- data/lib/plutonium/definition/page_widths.rb +65 -0
- data/lib/plutonium/definition/positioning.rb +126 -0
- data/lib/plutonium/definition/sorting.rb +17 -2
- data/lib/plutonium/helpers/turbo_helper.rb +7 -0
- data/lib/plutonium/interaction/README.md +61 -24
- data/lib/plutonium/interaction/async/configuration.rb +38 -0
- data/lib/plutonium/interaction/async/context.rb +419 -0
- data/lib/plutonium/interaction/async/executor.rb +422 -0
- data/lib/plutonium/interaction/async/job.rb +80 -0
- data/lib/plutonium/interaction/async/reap_job.rb +81 -0
- data/lib/plutonium/interaction/async/run.rb +394 -0
- data/lib/plutonium/interaction/async/run_definition.rb +155 -0
- data/lib/plutonium/interaction/async/run_policy.rb +86 -0
- data/lib/plutonium/interaction/base.rb +34 -7
- data/lib/plutonium/interaction/concerns/dispatchable.rb +518 -0
- data/lib/plutonium/interaction/concerns/scoping.rb +70 -9
- data/lib/plutonium/interaction/response/redirect.rb +11 -3
- data/lib/plutonium/kanban/board.rb +14 -0
- data/lib/plutonium/kanban/column.rb +4 -2
- data/lib/plutonium/kanban/dsl.rb +4 -1
- data/lib/plutonium/kanban/grouping.rb +9 -22
- data/lib/plutonium/kanban/positioning.rb +5 -65
- data/lib/plutonium/positioning/config.rb +94 -0
- data/lib/plutonium/positioning/model.rb +128 -0
- data/lib/plutonium/positioning.rb +25 -86
- data/lib/plutonium/railtie.rb +1 -0
- data/lib/plutonium/resource/controller.rb +118 -38
- data/lib/plutonium/resource/controllers/crud_actions/index_action.rb +32 -2
- data/lib/plutonium/resource/controllers/crud_actions.rb +30 -2
- data/lib/plutonium/resource/controllers/eager_loading.rb +87 -0
- data/lib/plutonium/resource/controllers/export_csv.rb +10 -1
- data/lib/plutonium/resource/controllers/kanban_actions.rb +53 -14
- data/lib/plutonium/resource/controllers/position_actions.rb +390 -0
- data/lib/plutonium/resource/controllers/presentable.rb +19 -13
- data/lib/plutonium/resource/controllers/queryable.rb +5 -1
- data/lib/plutonium/resource/controllers/wizard_actions.rb +21 -0
- data/lib/plutonium/resource/policy.rb +33 -0
- data/lib/plutonium/resource/query_object.rb +36 -0
- data/lib/plutonium/routing/mapper_extensions.rb +100 -8
- data/lib/plutonium/routing/route_set_extensions.rb +15 -1
- data/lib/plutonium/routing/wizard_registration.rb +4 -0
- data/lib/plutonium/testing/resource_policy.rb +6 -2
- data/lib/plutonium/ui/action_button.rb +12 -7
- data/lib/plutonium/ui/actions_dropdown.rb +1 -1
- data/lib/plutonium/ui/block.rb +21 -1
- data/lib/plutonium/ui/breadcrumbs.rb +187 -55
- data/lib/plutonium/ui/component/methods.rb +5 -0
- data/lib/plutonium/ui/component/positionable.rb +112 -0
- data/lib/plutonium/ui/component/resolves_tags.rb +57 -0
- data/lib/plutonium/ui/component/section.rb +185 -0
- data/lib/plutonium/ui/display/base.rb +13 -1
- data/lib/plutonium/ui/display/components/formatted_value.rb +26 -0
- data/lib/plutonium/ui/display/components/section.rb +18 -0
- data/lib/plutonium/ui/display/resource.rb +141 -22
- data/lib/plutonium/ui/display/theme.rb +20 -1
- data/lib/plutonium/ui/export_button.rb +1 -1
- data/lib/plutonium/ui/form/base.rb +8 -7
- data/lib/plutonium/ui/form/components/intl_tel_input.rb +1 -1
- data/lib/plutonium/ui/form/components/section.rb +7 -62
- data/lib/plutonium/ui/form/components/uppy.rb +12 -1
- data/lib/plutonium/ui/form/concerns/renders_nested_resource_fields.rb +16 -3
- data/lib/plutonium/ui/form/concerns/renders_structured_inputs.rb +5 -1
- data/lib/plutonium/ui/form/query.rb +2 -4
- data/lib/plutonium/ui/form/resource.rb +92 -15
- data/lib/plutonium/ui/form/theme.rb +17 -0
- data/lib/plutonium/ui/form/wizard.rb +25 -1
- data/lib/plutonium/ui/grid/card.rb +79 -11
- data/lib/plutonium/ui/grid/resource.rb +47 -5
- data/lib/plutonium/ui/interaction/async/run_progress.rb +227 -0
- data/lib/plutonium/ui/interaction/async/running_banner.rb +65 -0
- data/lib/plutonium/ui/kanban/card.rb +2 -1
- data/lib/plutonium/ui/kanban/column.rb +12 -6
- data/lib/plutonium/ui/kanban/resource.rb +6 -7
- data/lib/plutonium/ui/layout/base.rb +10 -3
- data/lib/plutonium/ui/nav_grid_menu.rb +1 -0
- data/lib/plutonium/ui/page/base.rb +19 -0
- data/lib/plutonium/ui/page/edit.rb +4 -1
- data/lib/plutonium/ui/page/index.rb +69 -18
- data/lib/plutonium/ui/page/interactive_action.rb +5 -1
- data/lib/plutonium/ui/page/new.rb +4 -1
- data/lib/plutonium/ui/page/show.rb +27 -10
- data/lib/plutonium/ui/page/wizard.rb +10 -1
- data/lib/plutonium/ui/page/wizard_chooser.rb +36 -11
- data/lib/plutonium/ui/page_width.rb +58 -0
- data/lib/plutonium/ui/table/base.rb +34 -1
- data/lib/plutonium/ui/table/components/attachment.rb +1 -1
- data/lib/plutonium/ui/table/components/bulk_actions_toolbar.rb +32 -8
- data/lib/plutonium/ui/table/components/drag_handle.rb +120 -0
- data/lib/plutonium/ui/table/components/filter_form.rb +1 -4
- data/lib/plutonium/ui/table/components/filter_pills.rb +1 -1
- data/lib/plutonium/ui/table/components/row_actions_dropdown.rb +1 -1
- data/lib/plutonium/ui/table/resource.rb +50 -5
- data/lib/plutonium/ui/table/theme.rb +59 -2
- data/lib/plutonium/ui/wizard/review.rb +4 -2
- data/lib/plutonium/ui/wizard/summary_display.rb +42 -14
- data/lib/plutonium/version.rb +1 -1
- data/lib/plutonium/wizard/attachments.rb +32 -197
- data/lib/plutonium/wizard/base.rb +6 -1
- data/lib/plutonium/wizard/configuration.rb +12 -0
- data/lib/plutonium/wizard/controller.rb +14 -0
- data/lib/plutonium/wizard/driving.rb +99 -15
- data/lib/plutonium/wizard/dsl.rb +23 -0
- data/lib/plutonium/wizard/resume.rb +127 -49
- data/lib/plutonium/wizard/runner.rb +46 -2
- data/lib/plutonium/wizard/step_adapter.rb +1 -1
- data/lib/plutonium/wizard/sweep_job.rb +16 -0
- data/lib/plutonium.rb +21 -0
- data/lib/rodauth/features/session_isolation.rb +92 -0
- data/lib/rodauth/plugins.rb +1 -0
- data/package.json +2 -1
- data/plutonium.gemspec +20 -20
- data/src/css/components.css +89 -1
- data/src/css/slim_select.css +20 -0
- data/src/js/controllers/breadcrumbs_controller.js +112 -0
- data/src/js/controllers/bulk_actions_controller.js +10 -2
- data/src/js/controllers/kanban_controller.js +30 -21
- data/src/js/controllers/positioned_controller.js +452 -0
- data/src/js/controllers/register_controllers.js +6 -0
- data/src/js/controllers/resource_drop_down_controller.js +5 -0
- data/src/js/controllers/run_progress_controller.js +73 -0
- data/src/js/drag/sortable.js +186 -0
- data/yarn.lock +108 -63
- metadata +101 -22
- data/docs/.vitepress/theme/components/HomeAudienceSplit.vue +0 -53
- data/docs/.vitepress/theme/components/HomePillars.vue +0 -42
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Async interactions — design
|
|
2
|
+
|
|
3
|
+
Status: design approved in conversation, not yet implemented.
|
|
4
|
+
|
|
5
|
+
## The problem
|
|
6
|
+
|
|
7
|
+
`Plutonium::Resource::Interaction` runs inline. `Interaction.call` validates, runs
|
|
8
|
+
`execute`, and returns an `Outcome` the controller turns into a `Response` — all
|
|
9
|
+
inside one request. That is correct for the operations it was built for, and
|
|
10
|
+
wrong for three kinds of work an app inevitably grows:
|
|
11
|
+
|
|
12
|
+
- **Bulk over many records.** Archiving 500 projects in a request either times
|
|
13
|
+
out or holds a web worker hostage.
|
|
14
|
+
- **One slow operation.** Rebuilding an index, generating a report, calling a
|
|
15
|
+
third-party API. Duration is opaque and often exceeds a request budget.
|
|
16
|
+
- **Anything worth auditing.** Nothing records who ran what, with which options,
|
|
17
|
+
or what it did. The interaction leaves no trace.
|
|
18
|
+
|
|
19
|
+
There is no `perform_later` anywhere in `lib/plutonium` outside
|
|
20
|
+
`Wizard::SweepJob`, so this is genuinely absent rather than partially built.
|
|
21
|
+
|
|
22
|
+
## The idea
|
|
23
|
+
|
|
24
|
+
**The run becomes a record.** That single change buys everything else:
|
|
25
|
+
persistence outlives the request, so async is natural; a row has a lifecycle, so
|
|
26
|
+
progress and scheduling follow; it is queryable, so audit is free; and it is a
|
|
27
|
+
resource, so it gets an index and show page from machinery that already exists.
|
|
28
|
+
|
|
29
|
+
Prior art is Bullet Train's Action Models. The adaptation below is deliberately
|
|
30
|
+
not a port — Plutonium already owns primitives (policy scopes, entity scoping,
|
|
31
|
+
interactions, `Kanban::Broadcaster`) that Bullet Train has to reinvent.
|
|
32
|
+
|
|
33
|
+
**Interactions dispatch; runs execute.** The interaction keeps its current job —
|
|
34
|
+
declare inputs, validate, authorize, render a form. On `execute` it creates a run
|
|
35
|
+
and enqueues it. It still succeeds *synchronously*, because dispatching is what
|
|
36
|
+
succeeded, so `Outcome` needs no new state. Its response is a redirect to the
|
|
37
|
+
run.
|
|
38
|
+
|
|
39
|
+
## Architecture
|
|
40
|
+
|
|
41
|
+
### Storage — one table, STI
|
|
42
|
+
|
|
43
|
+
A single `plutonium_interaction_runs` table with a `type` column. Authors
|
|
44
|
+
subclass to configure behaviour; options live in a JSON column, so an async
|
|
45
|
+
action costs zero migrations.
|
|
46
|
+
|
|
47
|
+
| Column | Purpose |
|
|
48
|
+
|---|---|
|
|
49
|
+
| `type` | STI discriminator — the author's run subclass |
|
|
50
|
+
| `state` | `pending` / `running` / `completed` / `failed` |
|
|
51
|
+
| `options` | JSON. The dispatching interaction's validated inputs |
|
|
52
|
+
| `target_type` | Class name of the targeted resource; `nil` for opaque runs |
|
|
53
|
+
| `target_ids` | JSON array of ids selected at dispatch |
|
|
54
|
+
| `initiator_type` / `initiator_id` | Polymorphic — who started it |
|
|
55
|
+
| `scoped_entity_type` / `scoped_entity_id` | Polymorphic, nullable — tenant context |
|
|
56
|
+
| `progress_total` / `progress_done` | Counts. Both `nil` for opaque work |
|
|
57
|
+
| `errors_log` | JSON. Per-target failures |
|
|
58
|
+
| `started_at` / `finished_at` | Timing, and the basis for sweeping |
|
|
59
|
+
|
|
60
|
+
The shape follows `Wizard::Session`, which is already a persisted process record
|
|
61
|
+
with polymorphic `owner` / `anchor` / `scope` refs and a `SweepJob`. This is not
|
|
62
|
+
a new architecture for the codebase; it is the second instance of one that works.
|
|
63
|
+
|
|
64
|
+
### Two run shapes
|
|
65
|
+
|
|
66
|
+
The framework picks by what the subclass implements — no mode flag.
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
class ArchiveProjectsRun < Plutonium::Interaction::Run
|
|
70
|
+
on_failure :continue
|
|
71
|
+
|
|
72
|
+
# Targets arrive from the dispatching interaction's selection. The framework
|
|
73
|
+
# re-resolves them through the policy scope before each call.
|
|
74
|
+
def perform_on(project) = project.archive(notify: options[:notify_users])
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
class RebuildSearchIndexRun < Plutonium::Interaction::Run
|
|
78
|
+
# No targets: one opaque unit, indeterminate progress.
|
|
79
|
+
def perform = SearchIndex.rebuild!
|
|
80
|
+
end
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
There is deliberately **no author-defined `targets` method**. Bullet Train needs
|
|
84
|
+
`valid_targets` because its action model receives raw ids with nothing to
|
|
85
|
+
constrain them. Plutonium already has that primitive: a bulk action's records
|
|
86
|
+
come from the selection, and `relation_scope` answers "which records may this
|
|
87
|
+
user touch". A second declaration would be a second place to disagree with the
|
|
88
|
+
policy.
|
|
89
|
+
|
|
90
|
+
### Identifying targets — ids, not GlobalIDs
|
|
91
|
+
|
|
92
|
+
`target_type` (a real column) plus a JSON array of ids. Not GIDs:
|
|
93
|
+
|
|
94
|
+
1. **The index feature needs a real column.** "Runs in progress for this
|
|
95
|
+
resource" is `where(target_type: Project.name, state: :running)`. That cannot
|
|
96
|
+
be indexed out of a JSON array of `gid://app/Project/1` strings.
|
|
97
|
+
2. **One scoped query, not N locates.** `policy_scope(Project).where(id: ids)`
|
|
98
|
+
re-resolves through `relation_scope` in a single query and enforces
|
|
99
|
+
authorization by construction. `GlobalID::Locator` bypasses the policy scope
|
|
100
|
+
entirely, requiring a separate re-authorization per record.
|
|
101
|
+
3. **GIDs solve heterogeneity that does not exist here.** Bulk selection is
|
|
102
|
+
always one resource type; the existing route is already `ids[]=1&ids[]=2`.
|
|
103
|
+
|
|
104
|
+
GlobalID-style polymorphism is still right for `initiator` and `scoped_entity`,
|
|
105
|
+
which genuinely vary by portal — hence the polymorphic columns.
|
|
106
|
+
|
|
107
|
+
### Authorization outside the request
|
|
108
|
+
|
|
109
|
+
This is the sharpest correctness issue in the design.
|
|
110
|
+
|
|
111
|
+
`perform` runs in a job. There is no `current_user`, no request, no ambient
|
|
112
|
+
context. Plutonium authorizes on the **pair** `(user, entity_scope)` —
|
|
113
|
+
`Authorizable` registers `authorize :entity_scope, through:
|
|
114
|
+
:entity_scope_for_authorize`, and PR #74 exists precisely because a nil entity
|
|
115
|
+
leaking into that memo broke scoping.
|
|
116
|
+
|
|
117
|
+
Therefore:
|
|
118
|
+
|
|
119
|
+
- The run persists **both** `initiator` and `scoped_entity`. Storing only the
|
|
120
|
+
user would re-resolve targets under the wrong tenant — and that fails *open*,
|
|
121
|
+
because a broader scope silently includes records the initiator could not see
|
|
122
|
+
when they dispatched.
|
|
123
|
+
- The job **rebuilds** the context from those columns. It never inherits one.
|
|
124
|
+
- Authorization is **re-checked at perform time**, not trusted from dispatch.
|
|
125
|
+
Access revoked between enqueue and run must stop the work, or a persisted run
|
|
126
|
+
becomes a way to launder stale permissions.
|
|
127
|
+
|
|
128
|
+
**Decision: `initiator` is required.** A nullable column would silently come to
|
|
129
|
+
mean "unscoped", which is the failure mode above. System-triggered runs
|
|
130
|
+
(schedules, callbacks) should get an explicit system initiator later rather than
|
|
131
|
+
a null.
|
|
132
|
+
|
|
133
|
+
### Failure policy
|
|
134
|
+
|
|
135
|
+
Configured on the run subclass, since the author knows the semantics:
|
|
136
|
+
|
|
137
|
+
- `on_failure :continue` — record the failure in `errors_log`, keep going.
|
|
138
|
+
Correct for bulk, where 497 successes should not be discarded.
|
|
139
|
+
- `on_failure :halt` — stop at the first error.
|
|
140
|
+
- `on_failure :transactional` — wrap the whole run in one transaction. Honest
|
|
141
|
+
about its cost: a long transaction over many records risks lock contention,
|
|
142
|
+
and is impossible once the work touches a third-party API.
|
|
143
|
+
|
|
144
|
+
**Decision: a target that vanishes or falls out of scope between dispatch and
|
|
145
|
+
perform is recorded as a per-target failure, not skipped.** "3 targets were no
|
|
146
|
+
longer available" is information the operator needs; silence there is how bulk
|
|
147
|
+
operations quietly under-apply.
|
|
148
|
+
|
|
149
|
+
### Progress transport
|
|
150
|
+
|
|
151
|
+
**Turbo poll by default, broadcast opt-in.** The progress page is a turbo-frame
|
|
152
|
+
with a refresh interval — no ActionCable, works on any deployment. A run may opt
|
|
153
|
+
into realtime and reuse `Kanban::Broadcaster`, whose stream names are already
|
|
154
|
+
keyed on **both** `resource_class` and `scoped_entity` so tenants can never share
|
|
155
|
+
a stream. This matches the house posture: kanban's `realtime` defaults to false.
|
|
156
|
+
|
|
157
|
+
### Surfaces
|
|
158
|
+
|
|
159
|
+
- **The run is a resource.** The framework ships a definition and policy, so a
|
|
160
|
+
portal can register it and get index and show pages. The progress page is
|
|
161
|
+
simply the run's show page — no bespoke view.
|
|
162
|
+
- **Dispatch redirects** to that show page, via the interaction's ordinary
|
|
163
|
+
`Response::Redirect`.
|
|
164
|
+
- **The target resource's index lists in-progress runs at the top**, queried by
|
|
165
|
+
`target_type` — which is why it is a column rather than JSON.
|
|
166
|
+
|
|
167
|
+
## Testing
|
|
168
|
+
|
|
169
|
+
- **Unit** — state machine, failure policies, progress accounting, options
|
|
170
|
+
round-tripping through JSON.
|
|
171
|
+
- **Authorization** — the highest-value tests. A run whose initiator lost access
|
|
172
|
+
between dispatch and perform must not perform. A run must not resolve targets
|
|
173
|
+
outside its stored `scoped_entity`. Both fail open if wrong, so both need
|
|
174
|
+
explicit coverage.
|
|
175
|
+
- **Integration** — dispatch returns a redirect; the show page reports progress;
|
|
176
|
+
the target index lists running work.
|
|
177
|
+
- **System** — one end-to-end pass on the progress page updating.
|
|
178
|
+
|
|
179
|
+
## Out of scope
|
|
180
|
+
|
|
181
|
+
Scheduling (`run_at`), approval workflows, retries, and import/export run types.
|
|
182
|
+
All are natural extensions of a persisted run and none is needed to make the
|
|
183
|
+
first version useful. Retention/sweeping follows `Wizard::SweepJob`'s pattern
|
|
184
|
+
when it is needed; runs are kept indefinitely until then, since audit is a
|
|
185
|
+
motivation.
|
|
@@ -3,29 +3,29 @@ GEM
|
|
|
3
3
|
specs:
|
|
4
4
|
action_text-trix (2.1.19)
|
|
5
5
|
railties
|
|
6
|
-
actioncable (8.1.3)
|
|
7
|
-
actionpack (= 8.1.3)
|
|
8
|
-
activesupport (= 8.1.3)
|
|
6
|
+
actioncable (8.1.3.1)
|
|
7
|
+
actionpack (= 8.1.3.1)
|
|
8
|
+
activesupport (= 8.1.3.1)
|
|
9
9
|
nio4r (~> 2.0)
|
|
10
10
|
websocket-driver (>= 0.6.1)
|
|
11
11
|
zeitwerk (~> 2.6)
|
|
12
|
-
actionmailbox (8.1.3)
|
|
13
|
-
actionpack (= 8.1.3)
|
|
14
|
-
activejob (= 8.1.3)
|
|
15
|
-
activerecord (= 8.1.3)
|
|
16
|
-
activestorage (= 8.1.3)
|
|
17
|
-
activesupport (= 8.1.3)
|
|
12
|
+
actionmailbox (8.1.3.1)
|
|
13
|
+
actionpack (= 8.1.3.1)
|
|
14
|
+
activejob (= 8.1.3.1)
|
|
15
|
+
activerecord (= 8.1.3.1)
|
|
16
|
+
activestorage (= 8.1.3.1)
|
|
17
|
+
activesupport (= 8.1.3.1)
|
|
18
18
|
mail (>= 2.8.0)
|
|
19
|
-
actionmailer (8.1.3)
|
|
20
|
-
actionpack (= 8.1.3)
|
|
21
|
-
actionview (= 8.1.3)
|
|
22
|
-
activejob (= 8.1.3)
|
|
23
|
-
activesupport (= 8.1.3)
|
|
19
|
+
actionmailer (8.1.3.1)
|
|
20
|
+
actionpack (= 8.1.3.1)
|
|
21
|
+
actionview (= 8.1.3.1)
|
|
22
|
+
activejob (= 8.1.3.1)
|
|
23
|
+
activesupport (= 8.1.3.1)
|
|
24
24
|
mail (>= 2.8.0)
|
|
25
25
|
rails-dom-testing (~> 2.2)
|
|
26
|
-
actionpack (8.1.3)
|
|
27
|
-
actionview (= 8.1.3)
|
|
28
|
-
activesupport (= 8.1.3)
|
|
26
|
+
actionpack (8.1.3.1)
|
|
27
|
+
actionview (= 8.1.3.1)
|
|
28
|
+
activesupport (= 8.1.3.1)
|
|
29
29
|
nokogiri (>= 1.8.5)
|
|
30
30
|
rack (>= 2.2.4)
|
|
31
31
|
rack-session (>= 1.0.1)
|
|
@@ -33,36 +33,36 @@ GEM
|
|
|
33
33
|
rails-dom-testing (~> 2.2)
|
|
34
34
|
rails-html-sanitizer (~> 1.6)
|
|
35
35
|
useragent (~> 0.16)
|
|
36
|
-
actiontext (8.1.3)
|
|
36
|
+
actiontext (8.1.3.1)
|
|
37
37
|
action_text-trix (~> 2.1.15)
|
|
38
|
-
actionpack (= 8.1.3)
|
|
39
|
-
activerecord (= 8.1.3)
|
|
40
|
-
activestorage (= 8.1.3)
|
|
41
|
-
activesupport (= 8.1.3)
|
|
38
|
+
actionpack (= 8.1.3.1)
|
|
39
|
+
activerecord (= 8.1.3.1)
|
|
40
|
+
activestorage (= 8.1.3.1)
|
|
41
|
+
activesupport (= 8.1.3.1)
|
|
42
42
|
globalid (>= 0.6.0)
|
|
43
43
|
nokogiri (>= 1.8.5)
|
|
44
|
-
actionview (8.1.3)
|
|
45
|
-
activesupport (= 8.1.3)
|
|
44
|
+
actionview (8.1.3.1)
|
|
45
|
+
activesupport (= 8.1.3.1)
|
|
46
46
|
builder (~> 3.1)
|
|
47
47
|
erubi (~> 1.11)
|
|
48
48
|
rails-dom-testing (~> 2.2)
|
|
49
49
|
rails-html-sanitizer (~> 1.6)
|
|
50
|
-
activejob (8.1.3)
|
|
51
|
-
activesupport (= 8.1.3)
|
|
50
|
+
activejob (8.1.3.1)
|
|
51
|
+
activesupport (= 8.1.3.1)
|
|
52
52
|
globalid (>= 0.3.6)
|
|
53
|
-
activemodel (8.1.3)
|
|
54
|
-
activesupport (= 8.1.3)
|
|
55
|
-
activerecord (8.1.3)
|
|
56
|
-
activemodel (= 8.1.3)
|
|
57
|
-
activesupport (= 8.1.3)
|
|
53
|
+
activemodel (8.1.3.1)
|
|
54
|
+
activesupport (= 8.1.3.1)
|
|
55
|
+
activerecord (8.1.3.1)
|
|
56
|
+
activemodel (= 8.1.3.1)
|
|
57
|
+
activesupport (= 8.1.3.1)
|
|
58
58
|
timeout (>= 0.4.0)
|
|
59
|
-
activestorage (8.1.3)
|
|
60
|
-
actionpack (= 8.1.3)
|
|
61
|
-
activejob (= 8.1.3)
|
|
62
|
-
activerecord (= 8.1.3)
|
|
63
|
-
activesupport (= 8.1.3)
|
|
59
|
+
activestorage (8.1.3.1)
|
|
60
|
+
actionpack (= 8.1.3.1)
|
|
61
|
+
activejob (= 8.1.3.1)
|
|
62
|
+
activerecord (= 8.1.3.1)
|
|
63
|
+
activesupport (= 8.1.3.1)
|
|
64
64
|
marcel (~> 1.0)
|
|
65
|
-
activesupport (8.1.3)
|
|
65
|
+
activesupport (8.1.3.1)
|
|
66
66
|
base64
|
|
67
67
|
bigdecimal
|
|
68
68
|
concurrent-ruby (~> 1.0, >= 1.3.1)
|
|
@@ -78,12 +78,12 @@ GEM
|
|
|
78
78
|
base64 (0.3.0)
|
|
79
79
|
bigdecimal (4.1.2)
|
|
80
80
|
builder (3.3.0)
|
|
81
|
-
concurrent-ruby (1.3.
|
|
81
|
+
concurrent-ruby (1.3.8)
|
|
82
82
|
connection_pool (3.0.2)
|
|
83
83
|
crass (1.0.7)
|
|
84
84
|
date (3.5.1)
|
|
85
85
|
drb (2.2.3)
|
|
86
|
-
erb (6.0.
|
|
86
|
+
erb (6.0.6)
|
|
87
87
|
erubi (1.13.1)
|
|
88
88
|
globalid (1.4.0)
|
|
89
89
|
activesupport (>= 6.1)
|
|
@@ -95,12 +95,12 @@ GEM
|
|
|
95
95
|
prism (>= 1.3.0)
|
|
96
96
|
rdoc (>= 4.0.0)
|
|
97
97
|
reline (>= 0.4.2)
|
|
98
|
-
json (2.
|
|
98
|
+
json (2.21.2)
|
|
99
99
|
logger (1.7.0)
|
|
100
|
-
loofah (2.25.
|
|
100
|
+
loofah (2.25.2)
|
|
101
101
|
crass (~> 1.0.2)
|
|
102
102
|
nokogiri (>= 1.12.0)
|
|
103
|
-
mail (2.9.
|
|
103
|
+
mail (2.9.1)
|
|
104
104
|
logger
|
|
105
105
|
mini_mime (>= 0.1.1)
|
|
106
106
|
net-imap
|
|
@@ -112,7 +112,7 @@ GEM
|
|
|
112
112
|
minitest (6.0.6)
|
|
113
113
|
drb (~> 2.0)
|
|
114
114
|
prism (~> 1.5)
|
|
115
|
-
net-imap (0.6.
|
|
115
|
+
net-imap (0.6.6)
|
|
116
116
|
date
|
|
117
117
|
net-protocol
|
|
118
118
|
net-pop (0.1.2)
|
|
@@ -161,30 +161,30 @@ GEM
|
|
|
161
161
|
rack (>= 1.3)
|
|
162
162
|
rackup (2.3.1)
|
|
163
163
|
rack (>= 3)
|
|
164
|
-
rails (8.1.3)
|
|
165
|
-
actioncable (= 8.1.3)
|
|
166
|
-
actionmailbox (= 8.1.3)
|
|
167
|
-
actionmailer (= 8.1.3)
|
|
168
|
-
actionpack (= 8.1.3)
|
|
169
|
-
actiontext (= 8.1.3)
|
|
170
|
-
actionview (= 8.1.3)
|
|
171
|
-
activejob (= 8.1.3)
|
|
172
|
-
activemodel (= 8.1.3)
|
|
173
|
-
activerecord (= 8.1.3)
|
|
174
|
-
activestorage (= 8.1.3)
|
|
175
|
-
activesupport (= 8.1.3)
|
|
164
|
+
rails (8.1.3.1)
|
|
165
|
+
actioncable (= 8.1.3.1)
|
|
166
|
+
actionmailbox (= 8.1.3.1)
|
|
167
|
+
actionmailer (= 8.1.3.1)
|
|
168
|
+
actionpack (= 8.1.3.1)
|
|
169
|
+
actiontext (= 8.1.3.1)
|
|
170
|
+
actionview (= 8.1.3.1)
|
|
171
|
+
activejob (= 8.1.3.1)
|
|
172
|
+
activemodel (= 8.1.3.1)
|
|
173
|
+
activerecord (= 8.1.3.1)
|
|
174
|
+
activestorage (= 8.1.3.1)
|
|
175
|
+
activesupport (= 8.1.3.1)
|
|
176
176
|
bundler (>= 1.15.0)
|
|
177
|
-
railties (= 8.1.3)
|
|
177
|
+
railties (= 8.1.3.1)
|
|
178
178
|
rails-dom-testing (2.3.0)
|
|
179
179
|
activesupport (>= 5.0.0)
|
|
180
180
|
minitest
|
|
181
181
|
nokogiri (>= 1.6)
|
|
182
|
-
rails-html-sanitizer (1.7.
|
|
183
|
-
loofah (~> 2.25)
|
|
182
|
+
rails-html-sanitizer (1.7.1)
|
|
183
|
+
loofah (~> 2.25, >= 2.25.2)
|
|
184
184
|
nokogiri (>= 1.15.7, != 1.16.7, != 1.16.6, != 1.16.5, != 1.16.4, != 1.16.3, != 1.16.2, != 1.16.1, != 1.16.0.rc1, != 1.16.0)
|
|
185
|
-
railties (8.1.3)
|
|
186
|
-
actionpack (= 8.1.3)
|
|
187
|
-
activesupport (= 8.1.3)
|
|
185
|
+
railties (8.1.3.1)
|
|
186
|
+
actionpack (= 8.1.3.1)
|
|
187
|
+
activesupport (= 8.1.3.1)
|
|
188
188
|
irb (~> 1.13)
|
|
189
189
|
rackup (>= 1.0.0)
|
|
190
190
|
rake (>= 12.2)
|
|
@@ -192,7 +192,7 @@ GEM
|
|
|
192
192
|
tsort (>= 0.2)
|
|
193
193
|
zeitwerk (~> 2.6)
|
|
194
194
|
rake (13.4.2)
|
|
195
|
-
rbs (4.
|
|
195
|
+
rbs (4.1.1)
|
|
196
196
|
logger
|
|
197
197
|
prism (>= 1.6.0)
|
|
198
198
|
tsort
|
|
@@ -215,7 +215,7 @@ GEM
|
|
|
215
215
|
base64
|
|
216
216
|
websocket-extensions (>= 0.1.0)
|
|
217
217
|
websocket-extensions (0.1.5)
|
|
218
|
-
zeitwerk (2.8.
|
|
218
|
+
zeitwerk (2.8.3)
|
|
219
219
|
|
|
220
220
|
PLATFORMS
|
|
221
221
|
aarch64-linux
|
|
@@ -236,40 +236,40 @@ DEPENDENCIES
|
|
|
236
236
|
|
|
237
237
|
CHECKSUMS
|
|
238
238
|
action_text-trix (2.1.19) sha256=7012f59421009cf284aa651294896414d653a61a2417c9b8714c8476d2f74009
|
|
239
|
-
actioncable (8.1.3) sha256=
|
|
240
|
-
actionmailbox (8.1.3) sha256=
|
|
241
|
-
actionmailer (8.1.3) sha256=
|
|
242
|
-
actionpack (8.1.3) sha256=
|
|
243
|
-
actiontext (8.1.3) sha256=
|
|
244
|
-
actionview (8.1.3) sha256=
|
|
245
|
-
activejob (8.1.3) sha256=
|
|
246
|
-
activemodel (8.1.3) sha256=
|
|
247
|
-
activerecord (8.1.3) sha256=
|
|
248
|
-
activestorage (8.1.3) sha256=
|
|
249
|
-
activesupport (8.1.3) sha256=
|
|
239
|
+
actioncable (8.1.3.1) sha256=e318528295c878a3efdfe25f0f2267c80cb7a76eba41bb5f64d44aa380a3d91b
|
|
240
|
+
actionmailbox (8.1.3.1) sha256=5f704972097d843ade8e435e93694a1dac732b926df1717aceba1f3840082b1c
|
|
241
|
+
actionmailer (8.1.3.1) sha256=88ea441b28ff02a0c6c006468892642a3d9942affce9d294e81a74504aa5c43c
|
|
242
|
+
actionpack (8.1.3.1) sha256=974cb7154548e81f470b1b0f247b99cb38e87825899dca58610596e2817723d0
|
|
243
|
+
actiontext (8.1.3.1) sha256=5da729d833d1a29cddb1eee938878e55e503d2613e00e735f5daf58c2ba98af2
|
|
244
|
+
actionview (8.1.3.1) sha256=2da68b8414c47b43bfbed1ce69c5afe1c04f78c267aacb5660a4cab5ca12cfb6
|
|
245
|
+
activejob (8.1.3.1) sha256=1c8dd275df930df40deecffec63d913a550a33fd94bd298f69721dd96939954a
|
|
246
|
+
activemodel (8.1.3.1) sha256=99cc02ce2faec371d14440949d85787ebd23a907c9baef0a9d4bcd4d21888f88
|
|
247
|
+
activerecord (8.1.3.1) sha256=0a2fb6c28f4938f6b013a3a549bec0a7e37d535f3dc8990e804bcc3258c0403b
|
|
248
|
+
activestorage (8.1.3.1) sha256=f555254f387b1cffa499d2fd3115d12635eadc5b15206a8534316a67036163ef
|
|
249
|
+
activesupport (8.1.3.1) sha256=85458765f25ea48b9019c46b6bb3fa5683197bf4280d9f06710a6e8d7a831376
|
|
250
250
|
base64 (0.3.0) sha256=27337aeabad6ffae05c265c450490628ef3ebd4b67be58257393227588f5a97b
|
|
251
251
|
bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
|
|
252
252
|
builder (3.3.0) sha256=497918d2f9dca528fdca4b88d84e4ef4387256d984b8154e9d5d3fe5a9c8835f
|
|
253
|
-
concurrent-ruby (1.3.
|
|
253
|
+
concurrent-ruby (1.3.8) sha256=b2f1be836e968ccc78ccfce277ea79c72a88633f22306782c16ff23fb415d1e1
|
|
254
254
|
connection_pool (3.0.2) sha256=33fff5ba71a12d2aa26cb72b1db8bba2a1a01823559fb01d29eb74c286e62e0a
|
|
255
255
|
crass (1.0.7) sha256=94868719948664c89ddcaf0a37c65048413dfcb1c869470a5f7a7ceb5390b295
|
|
256
256
|
date (3.5.1) sha256=750d06384d7b9c15d562c76291407d89e368dda4d4fff957eb94962d325a0dc0
|
|
257
257
|
drb (2.2.3) sha256=0b00d6fdb50995fe4a45dea13663493c841112e4068656854646f418fda13373
|
|
258
|
-
erb (6.0.
|
|
258
|
+
erb (6.0.6) sha256=a9b24986700f5bf127c4f297c5403c3ca41b83b0a316c0cd09a096b56e644ae5
|
|
259
259
|
erubi (1.13.1) sha256=a082103b0885dbc5ecf1172fede897f9ebdb745a4b97a5e8dc63953db1ee4ad9
|
|
260
260
|
globalid (1.4.0) sha256=037f12fbf1d9d7a014d501c2d5c77356fd4ddd96d7a7991d6700bba96706f427
|
|
261
261
|
i18n (1.15.2) sha256=00f9eb62412fe593b2a65a97daa75300d37abb8f7202ec748e94b6d46a9dd1b5
|
|
262
262
|
io-console (0.8.2) sha256=d6e3ae7a7cc7574f4b8893b4fca2162e57a825b223a177b7afa236c5ef9814cc
|
|
263
263
|
irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3
|
|
264
|
-
json (2.
|
|
264
|
+
json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
|
|
265
265
|
logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
|
|
266
|
-
loofah (2.25.
|
|
267
|
-
mail (2.9.
|
|
266
|
+
loofah (2.25.2) sha256=2007f746959ac65552456e04b433e83deb22759ab38c838b4445c70e43425918
|
|
267
|
+
mail (2.9.1) sha256=06574eca475253d6c18145dd70af80d0eb970182d55053497c5f4d797ea160e8
|
|
268
268
|
marcel (1.2.1) sha256=1678e9360e32f9eafa917c80029e2f6d10b2715c66a4b87b6d0da9b9cd1f859f
|
|
269
269
|
mini_mime (1.1.5) sha256=8681b7e2e4215f2a159f9400b5816d85e9d8c6c6b491e96a12797e798f8bccef
|
|
270
270
|
mini_portile2 (2.8.9) sha256=0cd7c7f824e010c072e33f68bc02d85a00aeb6fce05bb4819c03dfd3c140c289
|
|
271
271
|
minitest (6.0.6) sha256=153ea36d1d987a62942382b61075745042a2b3123b1cd48f4c3675af9cc7d6f1
|
|
272
|
-
net-imap (0.6.
|
|
272
|
+
net-imap (0.6.6) sha256=96aa4ee50df3060203e649efc341f53480b791d49e150f2fdebf68beb141a8df
|
|
273
273
|
net-pop (0.1.2) sha256=848b4e982013c15b2f0382792268763b748cce91c9e91e36b0f27ed26420dff3
|
|
274
274
|
net-protocol (0.2.2) sha256=aa73e0cba6a125369de9837b8d8ef82a61849360eba0521900e2c3713aa162a8
|
|
275
275
|
net-smtp (0.5.1) sha256=ed96a0af63c524fceb4b29b0d352195c30d82dd916a42f03c62a3a70e5b70736
|
|
@@ -298,12 +298,12 @@ CHECKSUMS
|
|
|
298
298
|
rack-session (2.1.2) sha256=595434f8c0c3473ae7d7ac56ecda6cc6dfd9d37c0b2b5255330aa1576967ffe8
|
|
299
299
|
rack-test (2.2.0) sha256=005a36692c306ac0b4a9350355ee080fd09ddef1148a5f8b2ac636c720f5c463
|
|
300
300
|
rackup (2.3.1) sha256=6c79c26753778e90983761d677a48937ee3192b3ffef6bc963c0950f94688868
|
|
301
|
-
rails (8.1.3) sha256=
|
|
301
|
+
rails (8.1.3.1) sha256=ccd11a36bfc171bf9c66d585d14c0ece91c0c9dde840aae60c0118d6f5c9c52a
|
|
302
302
|
rails-dom-testing (2.3.0) sha256=8acc7953a7b911ca44588bf08737bc16719f431a1cc3091a292bca7317925c1d
|
|
303
|
-
rails-html-sanitizer (1.7.
|
|
304
|
-
railties (8.1.3) sha256=
|
|
303
|
+
rails-html-sanitizer (1.7.1) sha256=e797a7c9b01e567307e317c576b49ab4168017e63eea4dba9ce3cb587e2f22c2
|
|
304
|
+
railties (8.1.3.1) sha256=2388a232579a00cefea4487de66c8553c3408c1300abdc6cf1799d86ffb04487
|
|
305
305
|
rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
|
|
306
|
-
rbs (4.
|
|
306
|
+
rbs (4.1.1) sha256=1bf118f2f95d1cd3956357645d495152b661e0257e57781d18ceecd461622b9e
|
|
307
307
|
rdoc (8.0.0) sha256=03bf8c08a9639658855a0cfd77c0abca8325c227693f7f33f82957811348c469
|
|
308
308
|
reline (0.6.3) sha256=1198b04973565b36ec0f11542ab3f5cfeeec34823f4e54cebde90968092b1835
|
|
309
309
|
securerandom (0.4.1) sha256=cc5193d414a4341b6e225f0cb4446aceca8e50d5e1888743fac16987638ea0b1
|
|
@@ -315,7 +315,7 @@ CHECKSUMS
|
|
|
315
315
|
useragent (0.16.11) sha256=700e6413ad4bb954bb63547fa098dddf7b0ebe75b40cc6f93b8d54255b173844
|
|
316
316
|
websocket-driver (0.8.2) sha256=97c556b019bf3410b4961002ac501621e9322d3f8a7bc02161a09301cc4c4146
|
|
317
317
|
websocket-extensions (0.1.5) sha256=1c6ba63092cda343eb53fc657110c71c754c56484aad42578495227d717a8241
|
|
318
|
-
zeitwerk (2.8.
|
|
318
|
+
zeitwerk (2.8.3) sha256=2c85125a8467ce069e20123d1e709a08955c9d29c118c25b46b7b7fafdbb92e5
|
|
319
319
|
|
|
320
320
|
BUNDLED WITH
|
|
321
321
|
4.0.6
|