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
- DOCUMENT_OPTIONS =
%i[ version locale media_type effective_at tenant from content resolver renderer link ].freeze
- VERSION =
"0.1.0"- 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
- .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.
448 |
# File 'lib/clickwrap.rb', line 448 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.
353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 |
# File 'lib/clickwrap.rb', line 353 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
462 |
# File 'lib/clickwrap.rb', line 462 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
191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/clickwrap.rb', line 191 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
204 205 206 207 208 209 210 211 212 213 214 215 |
# File 'lib/clickwrap.rb', line 204 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
315 316 317 318 319 320 321 322 |
# File 'lib/clickwrap.rb', line 315 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 --------------------------------------------------------
78 79 80 |
# File 'lib/clickwrap.rb', line 78 def config @config ||= Configuration.new end |
.configure {|config| ... } ⇒ Object
84 85 86 87 88 |
# File 'lib/clickwrap.rb', line 84 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.
298 299 300 301 302 303 304 305 |
# File 'lib/clickwrap.rb', line 298 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.
223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 |
# File 'lib/clickwrap.rb', line 223 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
406 |
# File 'lib/clickwrap.rb', line 406 def current?(policy_key, **) = verify(policy_key, **).success? |
.delete_recorded_browser_user_agent!(receipt, because:) ⇒ Object
435 436 437 |
# File 'lib/clickwrap.rb', line 435 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 ----------------------------------------------------------
431 432 433 |
# File 'lib/clickwrap.rb', line 431 def delete_recorded_ip_address!(receipt, because:) Retention::Disposition.delete_field!(receipt, :ip_address, because:) end |
.delete_recorded_ip_geolocation!(receipt, because:) ⇒ Object
439 440 441 |
# File 'lib/clickwrap.rb', line 439 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.
119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
# File 'lib/clickwrap.rb', line 119 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
179 180 181 182 183 |
# File 'lib/clickwrap.rb', line 179 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 -----------------------------------------------------------
110 |
# File 'lib/clickwrap.rb', line 110 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.
343 344 345 346 |
# File 'lib/clickwrap.rb', line 343 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
416 |
# File 'lib/clickwrap.rb', line 416 def export_receipt(receipt, **) = Receipt.export(receipt, **) |
.gem_version ⇒ Object
--- Internals ------------------------------------------------------------
461 |
# File 'lib/clickwrap.rb', line 461 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
372 373 374 375 376 377 378 379 380 381 382 |
# File 'lib/clickwrap.rb', line 372 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
384 385 386 387 388 389 390 391 392 393 |
# File 'lib/clickwrap.rb', line 384 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
470 471 472 |
# File 'lib/clickwrap.rb', line 470 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.
468 |
# File 'lib/clickwrap.rb', line 468 def now = Time.now.utc |
.policies ⇒ Object
111 |
# File 'lib/clickwrap.rb', line 111 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.
136 137 138 139 140 141 142 143 144 145 |
# File 'lib/clickwrap.rb', line 136 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
162 163 164 165 166 167 168 169 |
# File 'lib/clickwrap.rb', line 162 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 --------------------------------------------
187 188 189 |
# File 'lib/clickwrap.rb', line 187 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.
265 |
# File 'lib/clickwrap.rb', line 265 def protected_outcome(...) = ProtectedOutcome.build(...) |
.publish!(dry_run: false) ⇒ Object
--- Publishing -----------------------------------------------------------
455 |
# File 'lib/clickwrap.rb', line 455 def publish!(dry_run: false) = Services::PublishDocuments.new(dry_run:).call |
.receipt(event_id) ⇒ Object
414 |
# File 'lib/clickwrap.rb', line 414 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.
422 423 424 425 426 427 |
# File 'lib/clickwrap.rb', line 422 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.
249 250 251 252 253 254 255 256 257 258 259 260 |
# File 'lib/clickwrap.rb', line 249 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
307 308 309 310 311 312 313 |
# File 'lib/clickwrap.rb', line 307 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.
477 478 479 480 481 482 |
# File 'lib/clickwrap.rb', line 477 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
399 400 401 402 403 404 |
# File 'lib/clickwrap.rb', line 399 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.
412 |
# File 'lib/clickwrap.rb', line 412 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.
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 |
# File 'lib/clickwrap.rb', line 92 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.
151 152 153 154 155 156 157 158 159 160 |
# File 'lib/clickwrap.rb', line 151 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
171 172 173 174 175 176 177 |
# File 'lib/clickwrap.rb', line 171 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
112 |
# File 'lib/clickwrap.rb', line 112 def retention_classes = @retention_classes ||= Registry.new(:retention_class) |
.revoke!(statement_key, actor:, because:, subject: nil, tenant: nil, acting_for: nil, http_request: nil) ⇒ Object
324 325 326 327 328 329 |
# File 'lib/clickwrap.rb', line 324 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
278 |
# File 'lib/clickwrap.rb', line 278 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
331 332 333 334 335 336 |
# File 'lib/clickwrap.rb', line 331 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.
451 |
# File 'lib/clickwrap.rb', line 451 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.
270 271 272 |
# File 'lib/clickwrap.rb', line 270 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
274 275 276 |
# File 'lib/clickwrap.rb', line 274 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 ---------------------------------------------
397 |
# File 'lib/clickwrap.rb', line 397 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.
288 289 290 291 292 293 |
# File 'lib/clickwrap.rb', line 288 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 |