| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
The runtime writes seven tables and exposes none of them. An operator who wants to know why a message has not run reads SQL, and the only administration surfaces are two engine controllers and a handful of CLI commands. SolidObjects::Web is a mountable Rack application over those tables: instances and their committed state, the ready and claimed mailbox, reminders, effects, broadcasts, dead letters, and processes. It is a separate require, because a worker process must not carry a web stack. Authorization is the reason it is a router rather than a set of controllers. Every route declares the administration policy it needs and a route without one raises at load time, so a page added later cannot reach the actor tables before somebody says who may read it. The deny-by-default posture is enforced by construction rather than by remembering a filter. It changes only two things. Retrying a dead letter goes through DeadLetterManager, which is idempotent; a retry the mailbox refuses renders the reason rather than a 500. Pausing an instance sets paused_at so the activation manager stops claiming it, which is an operator brake and not a stop: a pass in flight finishes its turn and a synchronous caller waiting on that instance times out. Charts come from Chart.js on a CDN with a subresource integrity hash, and that host is the only external origin the policy names. A deployment without outbound network access vendors the file or turns charts off. Rack is now an explicit dependency at >= 3.1 because the dashboard writes lowercase response headers.
Greptile SummarySolidObjects::Web adds a separately loaded, mountable Rack operator dashboard for inspecting actor-runtime tables and performing narrowly authorized administrative actions.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains. Important Files Changed
Sequence DiagramsequenceDiagram
participant Operator
participant Rails as Rails Router / Session
participant Web as SolidObjects::Web
participant CSRF as CSRF Protection
participant Policy as Administration Policy
participant Runtime as Runtime Tables
Operator->>Rails: Dashboard request
Rails->>Web: Mounted Rack request with session
Web->>CSRF: Validate state-changing request
CSRF->>Policy: Forward accepted request
Policy-->>Web: Allow or deny route action/resource
alt Authorized
Web->>Runtime: Read data or perform narrow action
Runtime-->>Web: Result
Web-->>Operator: HTML, JSON, or redirect
else Denied
Web-->>Operator: 403 Forbidden
end
Reviews (2): Last reviewed commit: "fix: keep the CSRF secret for the sessio..." | Re-trigger Greptile |
Sorry, something went wrong.
| # The session token is replaced whether or not this comparison | ||
| # succeeds, so a token cannot be replayed after it is spent. | ||
| session[:csrf] = SecureRandom.base64(TOKEN_BYTES) | ||
| matches?(token, stored) |
There was a problem hiding this comment.
Session rotation invalidates valid forms
When an operator submits one of several forms rendered from the same session secret, valid? replaces that secret before comparison, causing every other form already open on the page or in another tab to receive 403 until it is reloaded.
Prompt To Fix With AIThis is a comment left during a code review.
Path: lib/solid_objects/web/csrf_protection.rb
Line: 67-70
Comment:
**Session rotation invalidates valid forms**
When an operator submits one of several forms rendered from the same session secret, `valid?` replaces that secret before comparison, causing every other form already open on the page or in another tab to receive 403 until it is reloaded.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.
Sorry, something went wrong.
The dead-letter list renders one Retry form per row, and a browser keeps pages open in other tabs. Rotating the session secret on the first submission answered 403 to every other form the same page had already rendered, so retrying a second dead letter appeared to be forbidden until the operator reloaded. Single use is not what a CSRF token provides. It proves the request came from a page this session was served; the per-request mask is what keeps the value on the wire from repeating, which is the property rotation looked like it was adding. The mount check now forges a token of the right length so it reaches the comparison rather than being turned away by the length check, which is what a wrong-but-well-formed token would do.
| Back | FazBrowse Home | New Git URL |
SolidObjects::Web is a mountable Rack dashboard over the runtime tables:
instances and their committed state, the ready and claimed mailbox, reminders,
effects, broadcasts, dead letters, and processes.
It is a separate require. A worker process must not carry a web stack, and
test/integration/load_contract_test.rb records each new file as deliberately
deferred.
API
New public surface: SolidObjects::Web.call (the Rack entry point),
.register for extension tabs, routes and view directories, .use for Rack
middleware, and .chart_library_url / .chart_library_integrity.
rack becomes an explicit dependency at >= 3.1. It already arrives with
Action Pack in every supported Rails version; the floor is stated because the
dashboard writes lowercase response headers, which Rack 3 requires. The web/
directory is added to the gemspec file list.
No migration. Nothing in the runtime path changed.
Security
Every route declares the administration policy it needs, and Router#route
raises at load time on a route without one. A page added later therefore cannot
reach the actor tables before an application has said who may read it, so the
deny-by-default posture is enforced by construction rather than by remembering
a filter. An unconfigured mount returns 403 everywhere.
authorization_context: is the request object, which answers request,
session and env, so a policy can read the signed-in operator the way a
controller does. docs/dashboard.md lists the action and resource of every
page.
Other boundaries:
request without a valid one gets 403.
Chart.js CDN host is the only external origin named, and only when charts are
enabled. Chart data travels in a data- attribute, not an inline script.
actor type would otherwise close the attribute.
query string.
Correctness
Two write actions, both narrow:
A retry the mailbox refuses, such as an actor class that no longer exists,
renders the reason with 422 rather than failing the request.
honours. This is an operator brake, not a stop: a pass already in flight
finishes its turn, and a synchronous caller waiting on a paused instance
times out rather than receiving a result. Both are stated on the page and in
the docs.
Not included: bulk retry, because DeadLetterManager exposes no bulk
operation, and audit records of who pressed what. docs/roadmap.md records
both.
Cost
The summary bar issues one grouped count per subsystem on every page, and each
list page counts its own relation to page it. Instance counts per actor type
are one grouped query on the dashboard only, bounded to twelve rows. Actor type
suggestions come from the registry rather than a DISTINCT no adapter can
answer from an index. Process rows count activated instances in one grouped
query rather than one per row. HEAD / exists so an uptime monitor need not
load a whole page. The roadmap records that this was reasoned about rather than
benchmarked.
Validation
rake covers Minitest, Standard Ruby, RuboCop, RBS generation and validation,
Steep, and Brakeman; all clean. The 14 skips are the pre-existing PostgreSQL
and MySQL suites that skip on SQLite; this change adds none.
Coverage worth naming:
Rails router in a subprocess, not a mock environment: the mount supplies
SCRIPT_NAME, the Rails session middleware supplies the session CSRF needs,
and a dashboard nested below the engine mount is only reachable because the
engine cascades a path it does not serve. It asserts a forged token is
refused and changes nothing, and that a valid one redirects to the
mount-prefixed path and applies the write.
format the same number identically.
Every significant behaviour was mutation tested: authorization, CSRF, HTML
escaping, pause, retry, filtering, paging, formatting, chart data and the chart
container each fail when the implementation is removed.
Three defects were found this way and fixed:
so a replacement page was unreachable.
built stack is memoized.
bare 500.
Not verified
A chart that grows on repeated redraws was reported during development. The
canvas is now in a dedicated sized container with position: absolute, which
is what Chart.js requires and makes the resize feedback loop structurally
impossible. I could not reproduce the original growth headlessly at
devicePixelRatio: 2 over 60 resize and update rounds, so the fix is
structural rather than confirmed against a reproduction.