Module: Clickwrap
- Defined in:
- lib/clickwrap.rb,
lib/clickwrap/digest.rb,
lib/clickwrap/doctor.rb,
lib/clickwrap/engine.rb,
lib/clickwrap/errors.rb,
lib/clickwrap/linter.rb,
lib/clickwrap/macros.rb,
lib/clickwrap/policy.rb,
lib/clickwrap/capture.rb,
lib/clickwrap/privacy.rb,
lib/clickwrap/receipt.rb,
lib/clickwrap/testing.rb,
lib/clickwrap/version.rb,
lib/clickwrap/registry.rb,
lib/clickwrap/authority.rb,
lib/clickwrap/lifecycle.rb,
lib/clickwrap/presenter.rb,
lib/clickwrap/reference.rb,
lib/clickwrap/statement.rb,
lib/clickwrap/identifier.rb,
lib/clickwrap/submission.rb,
lib/clickwrap/vocabulary.rb,
lib/clickwrap/actor_proxy.rb,
lib/clickwrap/front_matter.rb,
lib/clickwrap/models/event.rb,
lib/clickwrap/receipt_html.rb,
lib/clickwrap/registration.rb,
lib/clickwrap/system_actor.rb,
lib/clickwrap/test_helpers.rb,
lib/clickwrap/verification.rb,
lib/clickwrap/view_helpers.rb,
lib/clickwrap/configuration.rb,
lib/clickwrap/current_state.rb,
lib/clickwrap/import/legacy.rb,
lib/clickwrap/reviewed_text.rb,
lib/clickwrap/canonical_json.rb,
lib/clickwrap/localized_text.rb,
lib/clickwrap/anonymous_actor.rb,
lib/clickwrap/integrity/chain.rb,
lib/clickwrap/models/document.rb,
lib/clickwrap/pending_receipt.rb,
lib/clickwrap/retention_class.rb,
lib/clickwrap/integrity/anchor.rb,
lib/clickwrap/receipt_verifier.rb,
lib/clickwrap/document_renderer.rb,
lib/clickwrap/import/fine_print.rb,
lib/clickwrap/models/chain_head.rb,
lib/clickwrap/models/legal_hold.rb,
lib/clickwrap/protected_outcome.rb,
lib/clickwrap/remediation_token.rb,
lib/clickwrap/retention/applier.rb,
lib/clickwrap/retention/planner.rb,
lib/clickwrap/controller_helpers.rb,
lib/clickwrap/dsl/policy_builder.rb,
lib/clickwrap/integrity/attestor.rb,
lib/clickwrap/document_definition.rb,
lib/clickwrap/integrity/timestamp.rb,
lib/clickwrap/models/presentation.rb,
lib/clickwrap/schema_requirements.rb,
lib/clickwrap/subject_fingerprint.rb,
app/helpers/clickwrap/engine_helper.rb,
lib/clickwrap/capture/event_builder.rb,
lib/clickwrap/dsl/retention_builder.rb,
lib/clickwrap/models/event_document.rb,
lib/clickwrap/models/receipt_access.rb,
lib/clickwrap/presentation_manifest.rb,
lib/clickwrap/retention/disposition.rb,
lib/clickwrap/models/event_statement.rb,
lib/clickwrap/models/external_action.rb,
lib/clickwrap/models/policy_revision.rb,
lib/clickwrap/models/statement_state.rb,
lib/clickwrap/services/load_policies.rb,
lib/clickwrap/durable_commit_callback.rb,
lib/clickwrap/form_builder_extensions.rb,
lib/clickwrap/import/external_receipt.rb,
lib/clickwrap/ip_geolocation/location.rb,
lib/clickwrap/ip_geolocation/resolver.rb,
lib/clickwrap/models/disposition_plan.rb,
lib/clickwrap/models/document_version.rb,
lib/clickwrap/models/request_evidence.rb,
lib/clickwrap/request_evidence_policy.rb,
lib/clickwrap/models/application_record.rb,
lib/clickwrap/models/recording_sequence.rb,
lib/generators/clickwrap/link_generator.rb,
lib/clickwrap/request_evidence_extractor.rb,
lib/clickwrap/services/publish_documents.rb,
lib/generators/clickwrap/views_generator.rb,
lib/clickwrap/document_renderers/markdown.rb,
lib/clickwrap/trusted_proxy_configuration.rb,
lib/generators/clickwrap/policy_generator.rb,
lib/clickwrap/ip_geolocation/null_resolver.rb,
lib/clickwrap/models/integrity_attestation.rb,
lib/generators/clickwrap/install_generator.rb,
lib/generators/clickwrap/upgrade_generator.rb,
lib/clickwrap/capture/presentation_verifier.rb,
lib/generators/clickwrap/document_generator.rb,
lib/clickwrap/ip_geolocation/static_resolver.rb,
lib/clickwrap/models/concerns/has_clickwraps.rb,
lib/clickwrap/models/statement_identity_lock.rb,
lib/generators/clickwrap/hardening_generator.rb,
app/controllers/clickwrap/captures_controller.rb,
app/controllers/clickwrap/receipts_controller.rb,
lib/clickwrap/integrity/attestation_reconciler.rb,
lib/clickwrap/document_renderers/markdown_rails.rb,
lib/clickwrap/ip_geolocation/trackdown_resolver.rb,
app/controllers/clickwrap/application_controller.rb,
app/controllers/clickwrap/withdrawals_controller.rb,
lib/clickwrap/services/authorize_external_action.rb,
lib/clickwrap/services/validate_policy_references.rb,
lib/clickwrap/integrations/organizations_authority.rb,
app/controllers/clickwrap/document_versions_controller.rb
Overview
Clickwrap
The evidence-and-assent layer for Rails: versioned agreements, acknowledgments, consent, declarations, attestations, and authorizations, captured with the exact content and presentation they were offered under, and committed in the same transaction as the action they authorize.
The public surface is intentionally small:
Clickwrap.configure { |config| ... } # one block, in an initializer
Clickwrap.document :terms, ... # immutable versioned content
Clickwrap.policy :signup do ... end # what the server offers and accepts
Clickwrap.retention :ordinary do ... # how long each part is kept
has_clickwraps # on the model that can act
form.clickwrap :signup, submit: "..." # render the controls and the action
Clickwrap.capture!(:signup, actor:, http_request:, submission:)
Clickwrap.capture_and!(:withdrawal, ...) { withdrawal.submit! }
user.clickwraps.agreed_to?(:terms)
Clickwrap.verify(:withdrawal, actor:, subject:)
What this gem does is evidence mechanics. What it does not do is decide whether your agreement is enforceable, whether consent is the right lawful basis, whether a document change is material, who someone really is, or how long you must keep anything. Those belong to the application and its counsel, and no configuration flag here can stand in for them.
Defined Under Namespace
Modules: Authority, CanonicalJson, ControllerHelpers, CurrentState, DSL, Digest, DocumentRenderers, EngineHelper, FormBuilderExtensions, FrontMatter, Generators, HasClickwraps, Identifier, Import, Integrations, Integrity, IpGeolocation, Lifecycle, Macros, Privacy, ProtectedOutcome, ReceiptVerifier, Reference, Registration, Retention, ReviewedText, SchemaRequirements, Services, SubjectFingerprint, TestHelpers, Testing, TrustedProxyConfiguration, Verification, ViewHelpers, Vocabulary Classes: AccessNotAuthorized, ActorProxy, AlreadyConsumedError, AlreadyWithdrawnError, AnonymousActor, AnswerInvalid, ApplicationController, ApplicationRecord, AuthorityDecision, AuthorityNotVerified, AuthorityRule, AuthorityVerifier, Capture, CaptureRefused, CapturesController, ChainHead, Configuration, ConfigurationError, DefinitionError, DispositionPlan, DispositionPlanInvalid, Doctor, Document, DocumentDefinition, DocumentDigestMismatchError, DocumentNotPublishedError, DocumentRenderer, DocumentVersion, DocumentVersionConflictError, DocumentVersionsController, DurableCommitCallback, Engine, Error, Event, EventDocument, EventStatement, EventWriteFailed, ExternalAction, ExternalActionAlreadyResolved, ExternalActionError, ImmutableEvidenceError, IntegrityAttestation, IntegrityCheckFailed, LegalHold, LegalHoldInEffect, LifecycleError, Linter, LocalizedText, MissingTranslation, NotDefinedError, NotWithdrawableError, OneTimeAuthorizationConflict, PendingReceipt, Policy, PolicyRevision, Presentation, PresentationExpired, PresentationInvalid, PresentationManifest, Presenter, Receipt, ReceiptAccess, ReceiptHtml, ReceiptInvalid, ReceiptNotCommitted, ReceiptsController, RecordingSequence, RegistrationFailed, Registry, RemediationInvalid, RemediationNotAuthorized, RemediationToken, ReplayRejected, RepresentedPartyCreationFailed, RequestEvidence, RequestEvidenceExtractor, RequestEvidencePolicy, RequestEvidenceUnavailable, RetentionClass, RetryableTransactionError, Statement, StatementIdentityLock, StatementState, Submission, SubmissionInvalid, SystemActor, UnknownPolicyError, UnknownReceiptSchema, UnknownRetentionClassError, UnknownStatementError, VerificationFailed, WithdrawalsController
Constant Summary collapse
- DEFAULT_RETENTION_CLASS_KEY =
The retention class every policy gets unless it names its own with
retain_with: evidence kept indefinitely, deletion always an explicit, reviewed act. Keeping is reversible; deleting is not. "evidence_kept_indefinitely"- DOCUMENT_OPTIONS =
%i[ version locale media_type effective_at tenant from content resolver renderer link ].freeze
- VERSION =
"0.3.1"- CANONICAL_SCHEMA_VERSION =
The canonical schema version for receipts, event digests, and presentation manifests. This is deliberately independent of VERSION: gem releases may come and go without changing how historical evidence is serialized, and a change here always means a new explicit schema plus a verifier that still reads every previously released version.
"clickwrap.receipt.v1"- VERIFIER_VERSION =
Bumped only when the receiver-side verification logic changes in a way an auditor should be able to see in a receipt.
"1"- AUTHORITY_DECISION_ATTRIBUTES =
%i[authorized source role verified_at details].freeze
Class Method Summary collapse
-
.anonymous_actor(identifier) ⇒ Object
A stable opaque identifier for someone who is not a persisted record.
-
.authorize_external_action!(policy_key, after_pending_action_is_saved_inside_transaction: nil, &local_transaction_block) ⇒ Object
Captures evidence and commits a pending outbox row in one local transaction, for an action that has to cross a system boundary.
- .canonical_schema_version ⇒ Object
- .capture!(policy_key, actor:, subject: nil, tenant: nil, http_request: nil, submission: nil, answers: nil, locale: nil, capture_channel: nil, acting_for: nil, authentication_context: nil, attribution_method: nil, idempotency_key: nil) ⇒ Object
- .capture_and!(policy_key, actor:, subject: nil, tenant: nil, http_request: nil, submission: nil, answers: nil, locale: nil, capture_channel: nil, acting_for: nil, authentication_context: nil, attribution_method: nil, idempotency_key: nil) ⇒ Object
- .change_consent_scope!(statement_key, actor:, because:, subject: nil, tenant: nil, acting_for: nil, http_request: nil, submission: nil, answers: nil) ⇒ Object
-
.config ⇒ Object
(also: configuration)
--- Configuration --------------------------------------------------------.
- .configure {|config| ... } ⇒ Object
-
.correct_declaration!(statement_key, actor:, because:, subject: nil, tenant: nil, replaces: nil, acting_for: nil, http_request: nil, submission: nil, answers: nil) ⇒ Object
Correcting, renewing, and rescoping are new statements by the same person, so each takes the same
submission:a first capture does: they are captured through a real presentation, not flipped administratively. -
.create_represented_party!(policy_key, actor:, represented_party:, represented_party_creation_flow_id:, subject: nil, tenant: nil, http_request: nil, submission: nil, answers: nil, locale: nil, capture_channel: nil, authentication_context: nil, attribution_method: nil, idempotency_key: nil) ⇒ Object
A prospective represented-party flow for records such as a new customer organization.
- .current?(policy_key) ⇒ Boolean
- .delete_recorded_browser_user_agent!(receipt, because:) ⇒ Object
-
.delete_recorded_ip_address!(receipt, because:) ⇒ Object
--- Disposition ----------------------------------------------------------.
- .delete_recorded_ip_geolocation!(receipt, because:) ⇒ Object
- .doctor ⇒ Object
-
.document(key, **options) ⇒ Object
Declares one immutable document version.
- .document_definitions_for(key, tenant: nil) ⇒ Object
-
.documents ⇒ Object
--- Registries -----------------------------------------------------------.
-
.exempt!(policy_key, actor:, because:, subject: nil, tenant: nil) ⇒ Object
An explicitly recorded system exemption.
- .export_receipt(receipt) ⇒ Object
-
.gem_version ⇒ Object
--- Internals ------------------------------------------------------------.
- .import_external_receipt!(policy_key, actor:, provider_name:, provider_event_id:, provider_receipt: nil, verified_with: nil, verified_at: nil, occurred_at: nil, subject: nil, tenant: nil, because: nil, statements: nil) ⇒ Object
- .import_legacy!(policy_key, actor:, occurred_at:, because:, known: {}, unknown: [], dry_run: false, subject: nil, tenant: nil, statements: nil, capture_channel: "imported_provider", source: nil, counts_as_current: true) ⇒ Object
- .logger ⇒ Object
-
.now ⇒ Object
The server's own clock, used for every recorded time.
- .policies ⇒ Object
-
.policy(key, &block) ⇒ Object
Declares a server-owned policy.
- .policy!(key) ⇒ Object
-
.present(policy_key) ⇒ Object
--- Presentation and capture --------------------------------------------.
-
.protected_outcome ⇒ Object
Produces the strict, canonical result snapshot consumed by
record_protected_outcome_with. -
.publish!(dry_run: false) ⇒ Object
--- Publishing -----------------------------------------------------------.
- .receipt(event_id) ⇒ Object
-
.reconcile_missing_integrity_attestations!(scope: Event.all, retry_failed_attestations: false) ⇒ Object
Retry optional timestamp/anchor work that left no immutable result after a committed event.
-
.register!(policy_key, prospective_actor:, subject: nil, tenant: nil, http_request: nil, submission: nil, answers: nil, locale: nil, capture_channel: nil, acting_for: nil, authentication_context: nil, idempotency_key: nil, registration_flow_id: nil) ⇒ Object
Signup, modeled honestly: at first render there is no persisted actor, so the presentation binds to a short-lived registration flow, and the account and its evidence commit together.
- .renew!(statement_key, actor:, because:, subject: nil, tenant: nil, acting_for: nil, http_request: nil, submission: nil, answers: nil) ⇒ Object
-
.report_after_commit_failure(error, event) ⇒ Object
Post-commit hooks are observers, never authorization.
- .require!(policy_key) ⇒ Object
-
.required?(policy_key) ⇒ Boolean
True when the actor needs to complete this policy: either they have no current evidence, or a newer required document version has published since they last acted.
-
.reset! ⇒ Object
Reset all global state.
-
.retention(key, &block) ⇒ Object
Declares a retention class.
- .retention_class!(key) ⇒ Object
-
.retention_classes ⇒ Object
The registry is seeded with one built-in class: evidence kept indefinitely, nothing scheduled for deletion.
- .revoke!(statement_key, actor:, because:, subject: nil, tenant: nil, acting_for: nil, http_request: nil) ⇒ Object
- .submission_from(params) ⇒ Object
- .supersede!(statement_key, actor:, because: nil, subject: nil, tenant: nil, acting_for: nil, http_request: nil) ⇒ Object
-
.system_actor(name) ⇒ Object
A named non-human actor, for seeds, imports, and background processes.
-
.trusted_proxy_configuration_digest_for(trusted_proxies) ⇒ Object
Digest the effective proxy rules rather than a prose description of them.
- .trusted_proxy_configuration_digest_for_rails_application(application = Rails.application) ⇒ Object
-
.verify(policy_or_event) ⇒ Object
--- Verification and gating ---------------------------------------------.
-
.withdraw!(purpose_key, actor:, because:, tenant: nil, subject: nil, acting_for: nil, http_request: nil) ⇒ Object
--- Lifecycle ------------------------------------------------------------.
Class Method Details
.anonymous_actor(identifier) ⇒ Object
A stable opaque identifier for someone who is not a persisted record. The host owns the identifier and any later account linking. An IP address is not an actor identifier and Clickwrap will not accept one here.
464 |
# File 'lib/clickwrap.rb', line 464 def anonymous_actor(identifier) = AnonymousActor.new(identifier) |
.authorize_external_action!(policy_key, after_pending_action_is_saved_inside_transaction: nil, &local_transaction_block) ⇒ Object
Captures evidence and commits a pending outbox row in one local transaction, for an action that has to cross a system boundary. This is a distributed reliability protocol, not a cross-system ACID transaction — see Clickwrap::Services::AuthorizeExternalAction for exactly what it does and does not promise.
369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 |
# File 'lib/clickwrap.rb', line 369 def (policy_key, after_pending_action_is_saved_inside_transaction: nil, **, &local_transaction_block) if after_pending_action_is_saved_inside_transaction && local_transaction_block raise ArgumentError, "Pass either after_pending_action_is_saved_inside_transaction: or a block, not both." end local_transaction_hook = after_pending_action_is_saved_inside_transaction || local_transaction_block Services::AuthorizeExternalAction.new( policy: policy!(policy_key), after_pending_action_is_saved_inside_transaction: local_transaction_hook, ** ).call end |
.canonical_schema_version ⇒ Object
478 |
# File 'lib/clickwrap.rb', line 478 def canonical_schema_version = CANONICAL_SCHEMA_VERSION |
.capture!(policy_key, actor:, subject: nil, tenant: nil, http_request: nil, submission: nil, answers: nil, locale: nil, capture_channel: nil, acting_for: nil, authentication_context: nil, attribution_method: nil, idempotency_key: nil) ⇒ Object
207 208 209 210 211 212 213 214 215 216 217 218 |
# File 'lib/clickwrap.rb', line 207 def capture!(policy_key, actor:, subject: nil, tenant: nil, http_request: nil, submission: nil, answers: nil, locale: nil, capture_channel: nil, acting_for: nil, authentication_context: nil, attribution_method: nil, idempotency_key: nil) Capture.new( policy: policy!(policy_key), actor: actor, subject: subject, tenant: tenant, http_request: http_request, submission: submission, answers: answers, locale: locale, capture_channel: capture_channel, acting_for: acting_for, authentication_context: authentication_context, attribution_method: attribution_method, idempotency_key: idempotency_key ).capture! end |
.capture_and!(policy_key, actor:, subject: nil, tenant: nil, http_request: nil, submission: nil, answers: nil, locale: nil, capture_channel: nil, acting_for: nil, authentication_context: nil, attribution_method: nil, idempotency_key: nil) ⇒ Object
220 221 222 223 224 225 226 227 228 229 230 231 |
# File 'lib/clickwrap.rb', line 220 def capture_and!(policy_key, actor:, subject: nil, tenant: nil, http_request: nil, submission: nil, answers: nil, locale: nil, capture_channel: nil, acting_for: nil, authentication_context: nil, attribution_method: nil, idempotency_key: nil, &) Capture.new( policy: policy!(policy_key), actor: actor, subject: subject, tenant: tenant, http_request: http_request, submission: submission, answers: answers, locale: locale, capture_channel: capture_channel, acting_for: acting_for, authentication_context: authentication_context, attribution_method: attribution_method, idempotency_key: idempotency_key ).capture_and!(&) end |
.change_consent_scope!(statement_key, actor:, because:, subject: nil, tenant: nil, acting_for: nil, http_request: nil, submission: nil, answers: nil) ⇒ Object
331 332 333 334 335 336 337 338 |
# File 'lib/clickwrap.rb', line 331 def (statement_key, actor:, because:, subject: nil, tenant: nil, acting_for: nil, http_request: nil, submission: nil, answers: nil) Lifecycle.(statement_key, actor: actor, because: because, subject: subject, tenant: tenant, acting_for: acting_for, http_request: http_request, submission: submission, answers: answers) end |
.config ⇒ Object Also known as: configuration
--- Configuration --------------------------------------------------------
82 83 84 |
# File 'lib/clickwrap.rb', line 82 def config @config ||= Configuration.new end |
.configure {|config| ... } ⇒ Object
88 89 90 91 92 |
# File 'lib/clickwrap.rb', line 88 def configure yield config if block_given? config.validate! config end |
.correct_declaration!(statement_key, actor:, because:, subject: nil, tenant: nil, replaces: nil, acting_for: nil, http_request: nil, submission: nil, answers: nil) ⇒ Object
Correcting, renewing, and rescoping are new statements by the same
person, so each takes the same submission: a first capture does: they
are captured through a real presentation, not flipped administratively.
314 315 316 317 318 319 320 321 |
# File 'lib/clickwrap.rb', line 314 def correct_declaration!(statement_key, actor:, because:, subject: nil, tenant: nil, replaces: nil, acting_for: nil, http_request: nil, submission: nil, answers: nil) Lifecycle.correct!(statement_key, actor: actor, because: because, subject: subject, tenant: tenant, replaces: replaces, acting_for: acting_for, http_request: http_request, submission: submission, answers: answers) end |
.create_represented_party!(policy_key, actor:, represented_party:, represented_party_creation_flow_id:, subject: nil, tenant: nil, http_request: nil, submission: nil, answers: nil, locale: nil, capture_channel: nil, authentication_context: nil, attribution_method: nil, idempotency_key: nil) ⇒ Object
A prospective represented-party flow for records such as a new customer organization. The form binds the record type and a server-owned browser flow before the record exists. This block must return a persisted record of that presented type after creating its host authority relationship; Clickwrap then verifies authority, rebinds the final stable reference, and commits all of it together.
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 |
# File 'lib/clickwrap.rb', line 239 def create_represented_party!(policy_key, actor:, represented_party:, represented_party_creation_flow_id:, subject: nil, tenant: nil, http_request: nil, submission: nil, answers: nil, locale: nil, capture_channel: nil, authentication_context: nil, attribution_method: nil, idempotency_key: nil, &) Capture.new( policy: policy!(policy_key), actor: actor, subject: subject, tenant: tenant, http_request: http_request, submission: submission, answers: answers, locale: locale, capture_channel: capture_channel, acting_for: represented_party, authentication_context: authentication_context, attribution_method: attribution_method, idempotency_key: idempotency_key, represented_party_creation_flow_id: represented_party_creation_flow_id ).create_represented_party!(&) end |
.current?(policy_key) ⇒ Boolean
422 |
# File 'lib/clickwrap.rb', line 422 def current?(policy_key, **) = verify(policy_key, **).success? |
.delete_recorded_browser_user_agent!(receipt, because:) ⇒ Object
451 452 453 |
# File 'lib/clickwrap.rb', line 451 def delete_recorded_browser_user_agent!(receipt, because:) Retention::Disposition.delete_field!(receipt, :browser_user_agent, because:) end |
.delete_recorded_ip_address!(receipt, because:) ⇒ Object
--- Disposition ----------------------------------------------------------
447 448 449 |
# File 'lib/clickwrap.rb', line 447 def delete_recorded_ip_address!(receipt, because:) Retention::Disposition.delete_field!(receipt, :ip_address, because:) end |
.delete_recorded_ip_geolocation!(receipt, because:) ⇒ Object
455 456 457 |
# File 'lib/clickwrap.rb', line 455 def delete_recorded_ip_geolocation!(receipt, because:) Retention::Disposition.delete_field!(receipt, :ip_geolocation, because:) end |
.document(key, **options) ⇒ Object
Declares one immutable document version. Declaring it does not publish it:
bin/rails clickwrap:publish reads the bytes once, digests them, and
freezes a database snapshot. Until then the declaration is a promise about
what will be published, and a policy that references an unpublished
document fails loudly rather than presenting nothing.
135 136 137 138 139 140 141 142 143 144 145 146 147 148 |
# File 'lib/clickwrap.rb', line 135 def document(key, **) unknown = .keys.map(&:to_sym) - DOCUMENT_OPTIONS unless unknown.empty? raise DefinitionError, "Document #{key.inspect} has unknown option#{"s" if unknown.many?} " \ "#{unknown.map { |option| "`#{option}:`" }.join(", ")}. Supported options are: " \ "#{DOCUMENT_OPTIONS.map { |option| "`#{option}:`" }.join(", ")}. Clickwrap never " \ "ignores document options." end definition = DocumentDefinition.new(key: key, **) documents.register(definition.identity, definition) definition end |
.document_definitions_for(key, tenant: nil) ⇒ Object
195 196 197 198 199 |
# File 'lib/clickwrap.rb', line 195 def document_definitions_for(key, tenant: nil) documents.values.select do |definition| definition.key == key.to_s && definition.tenant_key == tenant&.to_s end end |
.documents ⇒ Object
--- Registries -----------------------------------------------------------
114 |
# File 'lib/clickwrap.rb', line 114 def documents = @documents ||= Registry.new(:document) |
.exempt!(policy_key, actor:, because:, subject: nil, tenant: nil) ⇒ Object
An explicitly recorded system exemption. Seeds, imports, invitations, and
service accounts must never "accept" by omitting a browser parameter or by
fabricating a human click. An exemption says plainly that no human action
occurred, records who created it and why, and never satisfies
agreed_to? — it answers the separate exempted_from? question.
359 360 361 362 |
# File 'lib/clickwrap.rb', line 359 def exempt!(policy_key, actor:, because:, subject: nil, tenant: nil) Lifecycle.exempt!(policy_key, actor: actor, because: because, subject: subject, tenant: tenant) end |
.export_receipt(receipt) ⇒ Object
432 |
# File 'lib/clickwrap.rb', line 432 def export_receipt(receipt, **) = Receipt.export(receipt, **) |
.gem_version ⇒ Object
--- Internals ------------------------------------------------------------
477 |
# File 'lib/clickwrap.rb', line 477 def gem_version = VERSION |
.import_external_receipt!(policy_key, actor:, provider_name:, provider_event_id:, provider_receipt: nil, verified_with: nil, verified_at: nil, occurred_at: nil, subject: nil, tenant: nil, because: nil, statements: nil) ⇒ Object
388 389 390 391 392 393 394 395 396 397 398 |
# File 'lib/clickwrap.rb', line 388 def import_external_receipt!(policy_key, actor:, provider_name:, provider_event_id:, provider_receipt: nil, verified_with: nil, verified_at: nil, occurred_at: nil, subject: nil, tenant: nil, because: nil, statements: nil) Import::ExternalReceipt.new( policy: policy!(policy_key), actor: actor, provider_name: provider_name, provider_event_id: provider_event_id, provider_receipt: provider_receipt, verified_with: verified_with, verified_at: verified_at, occurred_at: occurred_at, subject: subject, tenant: tenant, because: because, statements: statements ).import! end |
.import_legacy!(policy_key, actor:, occurred_at:, because:, known: {}, unknown: [], dry_run: false, subject: nil, tenant: nil, statements: nil, capture_channel: "imported_provider", source: nil, counts_as_current: true) ⇒ Object
400 401 402 403 404 405 406 407 408 409 |
# File 'lib/clickwrap.rb', line 400 def import_legacy!(policy_key, actor:, occurred_at:, because:, known: {}, unknown: [], dry_run: false, subject: nil, tenant: nil, statements: nil, capture_channel: "imported_provider", source: nil, counts_as_current: true) Import::Legacy.new( policy: policy!(policy_key), actor: actor, occurred_at: occurred_at, because: because, known: known, unknown: unknown, dry_run: dry_run, subject: subject, tenant: tenant, statements: statements, capture_channel: capture_channel, source: source, counts_as_current: counts_as_current ).import! end |
.logger ⇒ Object
486 487 488 |
# File 'lib/clickwrap.rb', line 486 def logger defined?(::Rails) && ::Rails.respond_to?(:logger) ? ::Rails.logger : nil end |
.now ⇒ Object
The server's own clock, used for every recorded time. It is described in receipts as exactly that — time recorded by the application server — and never as trusted time, which is a different thing supplied by a different kind of provider.
484 |
# File 'lib/clickwrap.rb', line 484 def now = Time.now.utc |
.policies ⇒ Object
115 |
# File 'lib/clickwrap.rb', line 115 def policies = @policies ||= Registry.new(:policy) |
.policy(key, &block) ⇒ Object
Declares a server-owned policy. Compiles immediately so a mistake is a boot failure with a sentence explaining it, not a surprise at 3am.
152 153 154 155 156 157 158 159 160 161 |
# File 'lib/clickwrap.rb', line 152 def policy(key, &block) raise DefinitionError, "Clickwrap.policy needs a block" unless block builder = DSL::PolicyBuilder.new(key) builder.instance_eval(&block) compiled = builder.compile policies.register(compiled.key, compiled) compiled end |
.policy!(key) ⇒ Object
178 179 180 181 182 183 184 185 |
# File 'lib/clickwrap.rb', line 178 def policy!(key) policies.fetch(key.to_s) do raise UnknownPolicyError, "No policy named #{key.inspect}. Defined policies: " \ "#{policies.keys.sort.join(", ").presence || "(none)"}. Policies are declared with " \ "`Clickwrap.policy #{key.inspect} do ... end`, conventionally in config/clickwrap.rb." end end |
.present(policy_key) ⇒ Object
--- Presentation and capture --------------------------------------------
203 204 205 |
# File 'lib/clickwrap.rb', line 203 def present(policy_key, **) Presenter.new(policy: policy!(policy_key), **).present end |
.protected_outcome ⇒ Object
Produces the strict, canonical result snapshot consumed by
record_protected_outcome_with. Keeping this construction in the gem
prevents every host from inventing a subtly different hash contract.
281 |
# File 'lib/clickwrap.rb', line 281 def protected_outcome(...) = ProtectedOutcome.build(...) |
.publish!(dry_run: false) ⇒ Object
--- Publishing -----------------------------------------------------------
471 |
# File 'lib/clickwrap.rb', line 471 def publish!(dry_run: false) = Services::PublishDocuments.new(dry_run:).call |
.receipt(event_id) ⇒ Object
430 |
# File 'lib/clickwrap.rb', line 430 def receipt(event_id) = Receipt.find(event_id) |
.reconcile_missing_integrity_attestations!(scope: Event.all, retry_failed_attestations: false) ⇒ Object
Retry optional timestamp/anchor work that left no immutable result after a committed event. This is intentionally explicit: it can call external providers, so applications normally run it from a scheduled job or the matching rake task rather than hiding it in a read path.
438 439 440 441 442 443 |
# File 'lib/clickwrap.rb', line 438 def reconcile_missing_integrity_attestations!(scope: Event.all, retry_failed_attestations: false) Integrity::AttestationReconciler.new( scope: scope, retry_failed_attestations: retry_failed_attestations ).call end |
.register!(policy_key, prospective_actor:, subject: nil, tenant: nil, http_request: nil, submission: nil, answers: nil, locale: nil, capture_channel: nil, acting_for: nil, authentication_context: nil, idempotency_key: nil, registration_flow_id: nil) ⇒ Object
Signup, modeled honestly: at first render there is no persisted actor, so the presentation binds to a short-lived registration flow, and the account and its evidence commit together. The receipt records account-registration attribution rather than pretending someone was already authenticated.
A prospective actor must be new. A public form's typed email address is not proof that its visitor controls an existing account or lead row. Use a distinct pending-request record, confirm the email address, and only then capture for the verified actor through the ordinary authenticated path.
265 266 267 268 269 270 271 272 273 274 275 276 |
# File 'lib/clickwrap.rb', line 265 def register!(policy_key, prospective_actor:, subject: nil, tenant: nil, http_request: nil, submission: nil, answers: nil, locale: nil, capture_channel: nil, acting_for: nil, authentication_context: nil, idempotency_key: nil, registration_flow_id: nil, &) Capture.new( policy: policy!(policy_key), actor: nil, prospective_actor: prospective_actor, subject: subject, tenant: tenant, http_request: http_request, submission: submission, answers: answers, locale: locale, capture_channel: capture_channel, acting_for: acting_for, authentication_context: authentication_context, idempotency_key: idempotency_key, registration_flow_id: registration_flow_id ).register!(&) end |
.renew!(statement_key, actor:, because:, subject: nil, tenant: nil, acting_for: nil, http_request: nil, submission: nil, answers: nil) ⇒ Object
323 324 325 326 327 328 329 |
# File 'lib/clickwrap.rb', line 323 def renew!(statement_key, actor:, because:, subject: nil, tenant: nil, acting_for: nil, http_request: nil, submission: nil, answers: nil) Lifecycle.renew!(statement_key, actor: actor, because: because, subject: subject, tenant: tenant, acting_for: acting_for, http_request: http_request, submission: submission, answers: answers) end |
.report_after_commit_failure(error, event) ⇒ Object
Post-commit hooks are observers, never authorization. A failure here is reported and swallowed, because the evidence and the action it protected have already committed and nothing an analytics call does may undo them.
493 494 495 496 497 498 |
# File 'lib/clickwrap.rb', line 493 def report_after_commit_failure(error, event) config.report_after_commit_failure_with.call(error, event) rescue StandardError => error logger&.error("[clickwrap] after-commit failure reporter itself raised: #{error.class}") nil end |
.require!(policy_key) ⇒ Object
415 416 417 418 419 420 |
# File 'lib/clickwrap.rb', line 415 def require!(policy_key, **) result = verify(policy_key, **) raise VerificationFailed, result unless result.success? result end |
.required?(policy_key) ⇒ Boolean
True when the actor needs to complete this policy: either they have no current evidence, or a newer required document version has published since they last acted. The application decides which change is material; Clickwrap enforces the rule it is given.
428 |
# File 'lib/clickwrap.rb', line 428 def required?(policy_key, **) = !current?(policy_key, **) |
.reset! ⇒ Object
Reset all global state. Used by the test suite to keep examples isolated; also handy in a console when experimenting with configuration.
96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 |
# File 'lib/clickwrap.rb', line 96 def reset! @config = Configuration.new @documents = nil @policies = nil @retention_classes = nil # Every memoized verifier goes with the configuration it was built from. # A verifier that outlived a reset keeps signing and accepting tokens # under the previous secret, which is the kind of thing a test suite # papers over (by resetting it itself) and a console session discovers # the hard way. RemediationToken.reset_verifier! if defined?(RemediationToken) PresentationManifest.reset_verifier! if defined?(PresentationManifest) SchemaRequirements.reset! if defined?(SchemaRequirements) self end |
.retention(key, &block) ⇒ Object
Declares a retention class. Clickwrap does not choose retention periods and cannot tell you whether yours are right; it makes a reviewed decision executable, auditable, and separable — the core event's schedule is independent of the optional personal request evidence attached to it.
167 168 169 170 171 172 173 174 175 176 |
# File 'lib/clickwrap.rb', line 167 def retention(key, &block) raise DefinitionError, "Clickwrap.retention needs a block" unless block builder = DSL::RetentionBuilder.new(key) builder.instance_eval(&block) compiled = builder.compile retention_classes.register(compiled.key, compiled) compiled end |
.retention_class!(key) ⇒ Object
187 188 189 190 191 192 193 |
# File 'lib/clickwrap.rb', line 187 def retention_class!(key) retention_classes.fetch(key.to_s) do raise UnknownRetentionClassError, "No retention class named #{key.inspect}. Defined classes: " \ "#{retention_classes.keys.sort.join(", ").presence || "(none)"}." end end |
.retention_classes ⇒ Object
The registry is seeded with one built-in class: evidence kept
indefinitely, nothing scheduled for deletion. It exists so a policy that
never says retain_with has a real, inspectable retention class instead
of a hole — keeping is the reversible default; deletion is the reviewed
opt-in. A host wanting deletion clocks declares its own class and names
it on the policy. The seed survives every reload (see Registry#clear).
123 124 125 126 127 128 |
# File 'lib/clickwrap.rb', line 123 def retention_classes @retention_classes ||= Registry.new(:retention_class) do |registry| registry.register(DEFAULT_RETENTION_CLASS_KEY, RetentionClass.new(key: DEFAULT_RETENTION_CLASS_KEY, rules: {})) end end |
.revoke!(statement_key, actor:, because:, subject: nil, tenant: nil, acting_for: nil, http_request: nil) ⇒ Object
340 341 342 343 344 345 |
# File 'lib/clickwrap.rb', line 340 def revoke!(statement_key, actor:, because:, subject: nil, tenant: nil, acting_for: nil, http_request: nil) Lifecycle.revoke!(statement_key, actor: actor, because: because, subject: subject, tenant: tenant, acting_for: acting_for, http_request: http_request) end |
.submission_from(params) ⇒ Object
294 |
# File 'lib/clickwrap.rb', line 294 def submission_from(params, ...) = Submission.from_params(params, ...) |
.supersede!(statement_key, actor:, because: nil, subject: nil, tenant: nil, acting_for: nil, http_request: nil) ⇒ Object
347 348 349 350 351 352 |
# File 'lib/clickwrap.rb', line 347 def supersede!(statement_key, actor:, because: nil, subject: nil, tenant: nil, acting_for: nil, http_request: nil) Lifecycle.supersede!(statement_key, actor: actor, because: because, subject: subject, tenant: tenant, acting_for: acting_for, http_request: http_request) end |
.system_actor(name) ⇒ Object
A named non-human actor, for seeds, imports, and background processes.
467 |
# File 'lib/clickwrap.rb', line 467 def system_actor(name) = SystemActor.new(name) |
.trusted_proxy_configuration_digest_for(trusted_proxies) ⇒ Object
Digest the effective proxy rules rather than a prose description of
them. The Rails-specific helper includes Rails' actual defaults when the
application has not overridden action_dispatch.trusted_proxies.
286 287 288 |
# File 'lib/clickwrap.rb', line 286 def trusted_proxy_configuration_digest_for(trusted_proxies) TrustedProxyConfiguration.digest_for(trusted_proxies) end |
.trusted_proxy_configuration_digest_for_rails_application(application = Rails.application) ⇒ Object
290 291 292 |
# File 'lib/clickwrap.rb', line 290 def trusted_proxy_configuration_digest_for_rails_application(application = Rails.application) TrustedProxyConfiguration.digest_for_rails_application(application) end |
.verify(policy_or_event) ⇒ Object
--- Verification and gating ---------------------------------------------
413 |
# File 'lib/clickwrap.rb', line 413 def verify(policy_or_event, **) = Verification.verify(policy_or_event, **) |
.withdraw!(purpose_key, actor:, because:, tenant: nil, subject: nil, acting_for: nil, http_request: nil) ⇒ Object
--- Lifecycle ------------------------------------------------------------
Each of these spells out the keywords its target accepts rather than
forwarding **. A bare forward compiles fine and reads fine, and then an
editor shows ** where the argument list should be, a typo'd keyword
travels one method further before failing, and the public API of the gem
is documented only in the private method behind it.
304 305 306 307 308 309 |
# File 'lib/clickwrap.rb', line 304 def withdraw!(purpose_key, actor:, because:, tenant: nil, subject: nil, acting_for: nil, http_request: nil) Lifecycle.withdraw!(purpose_key, actor: actor, because: because, tenant: tenant, subject: subject, acting_for: acting_for, http_request: http_request) end |