Class: OpenLoam::PendingAction
- Inherits:
-
TenantRecord
- Object
- TenantRecord
- OpenLoam::PendingAction
- Includes:
- Auditable, Encryptable, Workflow
- Defined in:
- app/models/open_loam/pending_action.rb
Overview
A staged mutation awaiting human approval. When a caller runs under confirm-mode (an MCP tool acting for an AI agent — see OpenLoam.mutation_mode), a write is staged HERE as a preview instead of committing; a manager approves or rejects, and only approval executes it. This is OpenLoam's thesis made concrete: agent-triggered writes are gate-able.
The approval gate IS a workflow (OpenLoam::Workflow): pending → (manager) approve / reject → executed / failed. "Who may approve" lives in one declarative, role-gated place. The proposed changes are encrypted at rest (OpenLoam::Encryptable), because a staged change to an encrypted target field would otherwise sit here — and in this row's own audit — as plaintext, reopening the leak L-901 closed.
Constant Summary
Constants included from Auditable
Instance Method Summary collapse
-
#approve!(by:) ⇒ Object
Approve and execute, atomically enough: the transition is role-gated to a manager; execution runs in a transaction so a failure leaves NO partial write; the record ends "executed" or "failed".
-
#changeset ⇒ Object
changesetis a Hash in Ruby but an encrypted JSON string at rest. - #changeset=(value) ⇒ Object
-
#preview ⇒ Object
A structured before/after diff.
- #reject!(by:, reason: nil) ⇒ Object
Methods included from Workflow
#open_loam_workflow_state, #workflow_transitions_available
Methods included from Encryptable
Instance Method Details
#approve!(by:) ⇒ Object
Approve and execute, atomically enough: the transition is role-gated to a
manager; execution runs in a transaction so a failure leaves NO partial
write; the record ends "executed" or "failed". Executes as by, so the
TARGET's audit names the approving human — the person owns the change.
98 99 100 101 102 103 104 105 106 107 |
# File 'app/models/open_loam/pending_action.rb', line 98 def approve!(by:) OpenLoam.as_tenant(tenant, actor: by) do reject_self_approval!(by) self.reviewed_by_id = by.id self.reviewed_at = Time.current to_approved! # NotAuthorizedError if not a manager; InvalidTransitionError if not pending execute_and_record! end self end |
#changeset ⇒ Object
changeset is a Hash in Ruby but an encrypted JSON string at rest. The
class methods win over Encryptable's included module and reach it via super.
67 68 69 70 |
# File 'app/models/open_loam/pending_action.rb', line 67 def changeset raw = super raw.present? ? JSON.parse(raw) : {} end |
#changeset=(value) ⇒ Object
72 73 74 |
# File 'app/models/open_loam/pending_action.rb', line 72 def changeset=(value) super(value.nil? ? nil : value.to_json) end |
#preview ⇒ Object
A structured before/after diff. An encrypted target field shows "[encrypted]" on BOTH sides — a reviewer sees THAT a secret changes, never its value.
79 80 81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'app/models/open_loam/pending_action.rb', line 79 def preview target = load_target encrypted = encrypted_target_fields changeset.each_with_object({}) do |(field, proposed), diff| field = field.to_s diff[field] = if encrypted.include?(field) { "from" => (target ? "[encrypted]" : nil), "to" => "[encrypted]" } else { "from" => target&.read_attribute(field), "to" => proposed } end end end |
#reject!(by:, reason: nil) ⇒ Object
109 110 111 112 113 114 115 116 117 |
# File 'app/models/open_loam/pending_action.rb', line 109 def reject!(by:, reason: nil) OpenLoam.as_tenant(tenant, actor: by) do self.reviewed_by_id = by.id self.reviewed_at = Time.current self.result = [ "rejected", reason.presence ].compact.join(": ") to_rejected! end self end |