class RailsAiAdmin::Operations::Runner
Executes registered database operations through one atomic boundary.
Constants
- FORM_RECOVERY_WINDOW
- FormReference
-
id is the future OperationRun id; ref is what the form submits.
- FormSession
- ORIGINS
- Preview
- Proposal
- RESTRICTED_MESSAGE
Public Instance Methods
Source
# File lib/rails_ai_admin/operations/runner.rb, line 151 def execute(context:, intent_ref:, input:, human_confirmed: false, origin: :ui, binding: nil) execute_intent(context: context, intent_ref: intent_ref, input: input, human_confirmed: human_confirmed, origin: origin, binding: binding) end
Reauthorizes and consumes a human event atomically with the domain write. human_confirmed comes only from a trusted HTTP/Surface adapter, never from an assistant tool parameter. Origin is audit metadata, not authority.
Source
# File lib/rails_ai_admin/operations/runner.rb, line 157 def execute_admitted(claim:) raise ConfigurationError, "Expected a collection lease claim" unless claim.is_a?(Collections::Claim) Collections::Contract.ensure_outside_transaction! execute_intent(context: claim.context, intent_ref: claim.operation_run_id, input: claim.payload, origin: claim.origin, admission: claim) rescue AccessDenied => error Result.new(code: error.code) end
Internal worker entry: its authority is the admitted parent and lease, never another synthetic human confirmation. No HTTP adapter calls it.
Source
# File lib/rails_ai_admin/operations/runner.rb, line 48 def issue_form_intent(context:, resource:, operation:, target_id: nil, expected_version: nil) definition = RailsAiAdmin.operation_registry.fetch(resource, operation) raise ConfigurationError, "Form intents cover built-in create, update and destroy" if definition.custom? result = preview(context: context, resource: resource, operation: operation, target_id: target_id, expected_version: expected_version, origin: :ui) raise AccessDenied, result.code if result.is_a?(Result) now = Time.current id = SecureRandom.uuid ref = FormIntent.generate({ id: id, actor_ref: context.actor_ref, tenant_ref: context.tenant_ref, security_version: context.security_version, resource_key: resource.key.to_s, operation: operation.to_s, operation_version: operation_version(resource, operation), target_id: result.target_id, expected_version: result.expected_version, created_at: now.utc.iso8601(6), expires_at: (now + RailsAiAdmin.configuration.intent_ttl).utc.iso8601(6) }, recovery_window: FORM_RECOVERY_WINDOW) FormReference.new(id, ref) end
Native form pages run the same checks as prepare but write nothing. The signed reference becomes an intent on first submit (ADR 0025).
Source
# File lib/rails_ai_admin/operations/runner.rb, line 69 def materialize_form_intent(context:, reference:) return reference if FormIntent.uuid?(reference) payload = FormIntent.verify(reference) return unless payload && context.is_a?(Context) && payload["actor_ref"] == context.actor_ref && payload["tenant_ref"] == context.tenant_ref unless OperationRun.exists?(id: payload["id"]) OperationRun.transaction(requires_new: true) { OperationRun.create!(FormIntent.run_attributes(payload)) } end payload["id"] rescue ActiveRecord::RecordNotUnique payload["id"] end
Turns a submitted form reference into its OperationRun id, creating the proposed row from the signed payload the first time. A UUID passes through unchanged; anything unverifiable, or another ownerβs reference, returns nil. The row commits on its own so a rejected submit keeps the same intent for the next attempt, exactly as a GET-created one did.
Source
# File lib/rails_ai_admin/operations/runner.rb, line 21 def preflight(context:, resource:, operation:, target_id: nil, expected_version: nil, input: nil, origin: :ui, intent_id: nil) unless intent_id.nil? || intent_id.is_a?(String) && intent_id.match?(/\A[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\z/) raise ConfigurationError, "intent_id must be a lowercase UUID" end definition = RailsAiAdmin.operation_registry.fetch(resource, operation) if definition.custom? && definition.scope == :collection raise ConfigurationError, "Collection intents require fixed-manifest preflight and admission" end result = preview(context: context, resource: resource, operation: operation, target_id: target_id, expected_version: expected_version, input: input, origin: origin) return result if result.is_a?(Result) intent = OperationRun.create!(id: intent_id || SecureRandom.uuid, actor_ref: context.actor_ref, tenant_ref: context.tenant_ref, security_version: context.security_version, resource_key: resource.key.to_s, operation: operation.to_s, operation_version: operation_version(resource, operation), target_id: result.target_id, expected_version: result.expected_version, input_digest: input && InputSchema.digest(result.values), origin: origin.to_s, expires_at: Time.current + RailsAiAdmin.configuration.intent_ttl) Proposal.new(intent, result.values, result.changes) end
intent_id lets a replay-safe adapter (an assistant tool call) derive one stable intent per call; it must be a UUID and creates no authority.
Source
# File lib/rails_ai_admin/operations/runner.rb, line 13 def prepare(...) result = preflight(...) raise AccessDenied, result.code if result.is_a?(Result) result.intent end
Source
# File lib/rails_ai_admin/operations/runner.rb, line 120 def preview(context:, resource:, operation:, target_id: nil, expected_version: nil, input: nil, origin: :ui) operation = operation.to_s check_origin!(origin) definition = RailsAiAdmin.operation_registry.fetch(resource, operation) access = Authorization::Access.new(context, resource) ensure_connection!(resource, definition) read_proposal = lambda do record = operation == "create" ? access.relation.new : access.find!(target_id, action: operation) access.authorize!(operation, record) version = operation == "create" ? nil : parse_version(expected_version) raise AccessDenied, "CONFLICT" if version && version != RecordVersion.of(record) raise ConfigurationError, "Create must not have a target" if operation == "create" && target_id raise ConfigurationError, "Custom operations require input-bound preflight" if definition.custom? && input.nil? values = input.nil? ? nil : coerce!(access, record, definition, input) changes = definition.custom? ? preview_changes(access, record, definition, values) : {} [ record, version, values, changes ] end record, version, values, changes = definition.custom? ? ActiveRecord::Base.while_preventing_writes(&read_proposal) : read_proposal.call Preview.new(operation == "create" ? nil : record.id.to_s, version, values || {}, changes) rescue Rejection => error error.result rescue ExecutionContext::DeadlineExceeded Result.new(code: "TIMEOUT") rescue StandardError => error raise if !definition&.custom? || error.is_a?(ConfigurationError) || error.is_a?(AccessDenied) Result.new(code: "OPERATION_FAILED") end
Computes a safe typed preview without creating an intent or saving domain data. Collection admission and native member preparation share this path.
Source
# File lib/rails_ai_admin/operations/runner.rb, line 83 def resume_form(context:, resource:, intent_ref:) raise AccessDenied, "UNAUTHENTICATED" unless context.is_a?(Context) unless FormIntent.uuid?(intent_ref) # An unsubmitted form has no row yet; only its own recoverable kind # is materialized, then the same checks as a stored intent apply. payload = FormIntent.verify(intent_ref) raise AccessDenied unless payload && payload["resource_key"] == resource.key.to_s && %w[create update].include?(payload["operation"]) intent_ref = materialize_form_intent(context: context, reference: intent_ref) || raise(AccessDenied) end raise AccessDenied unless intent_ref.is_a?(String) && intent_ref.match?(/\A[0-9a-f-]{36}\z/) OperationRun.transaction(requires_new: true) do run = OperationRun.lock.find_by(id: intent_ref, actor_ref: context.actor_ref, tenant_ref: context.tenant_ref) unless run && run.resource_key == resource.key.to_s && %w[create update].include?(run.operation) && run.origin == "ui" && run.status == "proposed" && run.parent_run_id.nil? && run.selection_id.nil? && run.input_digest.nil? && run.confirmed_at.nil? && run.approved_by_ref.nil? && run.approved_digest.nil? raise AccessDenied end raise AccessDenied, "APPROVAL_EXPIRED" unless run.created_at && run.created_at > Time.current - FORM_RECOVERY_WINDOW definition = RailsAiAdmin.operation_registry.fetch(resource, run.operation) raise AccessDenied unless !definition.custom? && run.operation_version == operation_version(resource, run.operation) ensure_connection!(resource, definition) access = Authorization::Access.new(context, resource) record = load_record(access, run, definition) access.authorize!(run.operation, record) if run.operation == "update" && run.expected_version != RecordVersion.of(record) raise AccessDenied, "CONFLICT" end raise AccessDenied unless run.operation != "create" || run.target_id.nil? && run.expected_version.nil? definition.fields(access, record) run.update!(security_version: context.security_version, expires_at: [ Time.current + RailsAiAdmin.configuration.intent_ttl, run.created_at + FORM_RECOVERY_WINDOW ].min) FormSession.new(run, record) end end
Reopens an unconsumed native form after fresh authentication. No input, business write, approval or replacement intent is admitted here.