assinafy 1.5.0 → 1.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f8b8c2701ec7f853e14b297d675829d16fef94b35f87d46cf372ef24d2d55482
4
- data.tar.gz: d6cd90c3ad7304648f397544a118b9cd57dfd80db2a6a84ddea116878b515da8
3
+ metadata.gz: 37dc8933996b9192a9f12896d772d4d5f745169fcaf0bb13b06cd199e4650bcd
4
+ data.tar.gz: 70fe0b92c876f387aeb3656f18b0e78d3cbc09d41dbefc73bba96bb6177b423b
5
5
  SHA512:
6
- metadata.gz: 823b760bc2d019f2e6f77de43b6e199067b1030e5e1f2a75fde65ad87fa7cec51ae8dba59f357e388a27db945a814cc6ff0dd2be4be92e4d9c41a6b5cf5c272c
7
- data.tar.gz: 5007b5ec7928af7dd1cc736cba63f9fc8efb72aa1478aedfad08bcaa28af2eec0c248e7903732b6e4b4c76fd73cd17f47a64535a266e33370e8d56b2b424e615
6
+ metadata.gz: 5bd2d4cfc525e22c84d4de1cc232f4666b769bcdd66e21d56397ad1c4a61bc1c482a7a9b183dcadfde1bd098e624ada2776f847e2c55fcf65e43d20f89437d34
7
+ data.tar.gz: 4586c42fc917ef30c6e7eef7718310fd583ce2817acecdf2de5f3f972b405e8986c9a474aa5dbdd9f3c476b2651a700c641471c8154d710e85f2ba4c7c7ebbc1
data/CHANGELOG.md CHANGED
@@ -2,6 +2,54 @@
2
2
 
3
3
  All notable changes to the `assinafy` Ruby gem are documented here.
4
4
 
5
+ ## 1.5.2
6
+
7
+ ### Fixed
8
+
9
+ - `base_url` must now be an absolute `http`/`https` URL with a host. Other schemes, scheme-less
10
+ hosts, and relative paths raise `Assinafy::ValidationError` at construction instead of sending
11
+ credentials to them or surfacing a raw `URI::InvalidURIError`.
12
+ - `AssignmentResource.build_payload` now rejects a non-String `message` or `expires_at` and a
13
+ `copy_receivers` value that is not an array of non-empty signer IDs, instead of forwarding them
14
+ to the API verbatim.
15
+ - `Client#upload_and_request_signatures` validates the whole assignment body before uploading, so
16
+ malformed `message`, `expires_at`, or `copy_receivers` values no longer leave an uploaded
17
+ document and freshly created signers behind.
18
+
19
+ ### Changed
20
+
21
+ - Authentication and API-key operations route through the shared resource request helpers used by
22
+ every other resource.
23
+
24
+ ## 1.5.1
25
+
26
+ ### Added
27
+
28
+ - A complete document workflow in the README and an API reference covering all 89 published
29
+ operations, five supported template operations, request bodies, success responses, errors,
30
+ and all 39 published schemas.
31
+ - A weekly upstream contract check and packaged-gem installation smoke test.
32
+ - Published RBS signatures and Steep verification in CI and release checks.
33
+
34
+ ### Fixed
35
+
36
+ - Every client and resource request now sends the exact versioned
37
+ `Assinafy-Ruby-SDK/v1.5.1` User-Agent.
38
+ - Public and signer-authenticated operations no longer inherit workspace credentials.
39
+ - Collection and binary operations now reject malformed successful responses instead of
40
+ silently returning misleading values.
41
+ - Document and template uploads validate safe filenames, non-empty PDF content, and document
42
+ upload IDs before later writes.
43
+ - The high-level upload/signature workflow validates every signer before uploading and retains
44
+ created resource IDs in error context for explicit cleanup.
45
+ - Request validation now rejects malformed signer fields, assignment items, tag IDs, field
46
+ values, and signer-document identifiers before network calls.
47
+
48
+ ### Changed
49
+
50
+ - Development and release verification use Ruby 4.0.6 while CI retains Ruby 3.2 compatibility
51
+ and tests Ruby 3.3, 3.4, 4.0, and head.
52
+
5
53
  ## 1.5.0
6
54
 
7
55
  ### Added
@@ -53,9 +101,8 @@ All notable changes to the `assinafy` Ruby gem are documented here.
53
101
 
54
102
  ## 1.4.0
55
103
 
56
- This release is the result of a full audit of the SDK against the live Assinafy
57
- v1 API (verified end-to-end against the sandbox) and the published documentation
58
- at <https://api.assinafy.com.br/v1/docs>.
104
+ This release aligned the SDK with the live Assinafy v1 API and the published
105
+ documentation at <https://api.assinafy.com.br/v1/docs>.
59
106
 
60
107
  ### Added
61
108
 
@@ -92,7 +139,7 @@ at <https://api.assinafy.com.br/v1/docs>.
92
139
  ### Changed
93
140
 
94
141
  - **Minimum Ruby is now 3.2** (3.0 and 3.1 are end-of-life). CI tests 3.2, 3.3, 3.4,
95
- 4.0, and head. `.ruby-version` is committed (3.4.8) and drives the lint/audit/release jobs.
142
+ 4.0, and head. `.ruby-version` is committed (3.4.8) and drives the lint, security, and release jobs.
96
143
  - `SignerDocumentResource#list` accepts an optional `signer_access_code:` (the endpoint
97
144
  also supports workspace `X-Api-Key` auth); the class auth documentation was corrected.
98
145
  - CI gained a `concurrency` group to cancel superseded runs.
data/README.md CHANGED
@@ -5,11 +5,12 @@
5
5
 
6
6
  Ruby SDK for the [Assinafy API v1](https://api.assinafy.com.br/v1/docs).
7
7
 
8
- The SDK includes a public wrapper for every operation in the current Assinafy v1 [OpenAPI specification](https://api.assinafy.com.br/v1/docs/openapi.json), plus sandbox-live template routes that are not yet listed there. The checked-in [`spec/api_coverage_spec.rb`](spec/api_coverage_spec.rb) verifies that its static route-to-method inventory is unique and points to public SDK methods.
8
+ The SDK exposes every Assinafy API v1 operation and the complete supported template lifecycle. The checked-in [`spec/api_coverage_spec.rb`](spec/api_coverage_spec.rb) validates that each route maps uniquely to a public SDK method.
9
9
 
10
10
  - **Source:** <https://github.com/assinafy/ruby-sdk>
11
11
  - **Issues:** <https://github.com/assinafy/ruby-sdk/issues>
12
12
  - **API docs:** <https://api.assinafy.com.br/v1/docs>
13
+ - **Ruby SDK API reference:** [`docs/API_REFERENCE.md`](docs/API_REFERENCE.md)
13
14
 
14
15
  ## Requirements
15
16
 
@@ -81,9 +82,14 @@ client = Assinafy::Client.new(
81
82
 
82
83
  - `api_key:` sends `X-Api-Key` (preferred).
83
84
  - `token:` sends `Authorization: Bearer ...` (legacy session token).
85
+ - Configure exactly one credential. If both are supplied, the SDK sends only `X-Api-Key`.
84
86
  - A client can also be created with no credentials for authentication and public/signer endpoints.
87
+ - `base_url:` must be an absolute `http`/`https` URL. Anything else — a scheme-less host, a relative path, or
88
+ another scheme — raises `Assinafy::ValidationError` instead of attaching your credentials to it. A trailing
89
+ slash is stripped.
85
90
  - Account-scoped methods document a per-call account override for multi-workspace tenants.
86
91
  - Provide a `Logger`-compatible `logger:` to observe upload/assignment/webhook lifecycle messages.
92
+ - Requests send `User-Agent: Assinafy-Ruby-SDK/v<Assinafy::VERSION>`; the suffix always follows the gem version.
87
93
 
88
94
  `Client.from_config(hash)` accepts string- or symbol-keyed hashes (e.g. parsed YAML).
89
95
 
@@ -145,9 +151,12 @@ client.users.notification_preferences # returns all nine owner-e
145
151
  client.users.update_notification_preferences(SignerDeclined: false) # partial request; returns all nine
146
152
  ```
147
153
 
148
- Both stats methods return rows with `period`, `documents_uploaded`, `documents_sent`,
149
- `signature_requests`, `signature_requests_email`, `signature_requests_whatsapp`,
150
- `signature_requests_viewed`, `signature_requests_completed`, and `documents_certified`.
154
+ Both stats methods return rows with `period`, `documents_uploaded`, `documents_sent`, `signature_requests`,
155
+ `signature_requests_notification_email`, `signature_requests_notification_whatsapp`,
156
+ `signature_requests_notification_bypass`, `signature_requests_verification_email`,
157
+ `signature_requests_verification_whatsapp`, `signature_requests_verification_bypass`,
158
+ `signature_requests_verification_digital_certificate`, `signature_requests_viewed`,
159
+ `signature_requests_completed`, and `documents_certified`.
151
160
 
152
161
  ### Documents
153
162
 
@@ -174,7 +183,7 @@ client.documents.send_token('document-id', email: 'alice@example.com') # current
174
183
  client.documents.list_tags('document-id')
175
184
  client.documents.replace_tags('document-id', ['tag-id-1', 'tag-id-2'])
176
185
  client.documents.append_tags('document-id', ['tag-id-3'])
177
- # The current sandbox also accepts existing tag names in these arrays.
186
+ # The deployed sandbox also accepts existing tag names in these arrays.
178
187
  client.documents.detach_tag('document-id', 'tag-id')
179
188
 
180
189
  # Template-driven creation
@@ -204,7 +213,7 @@ client.signers.list(search: 'alice', per_page: 50) # returns { data:, meta: }
204
213
  client.signers.update('signer-id', full_name: 'Alice S.', government_id: '00000000000')
205
214
  client.signers.delete('signer-id')
206
215
 
207
- # Convenience: case-insensitive lookup with built-in 404 handling
216
+ # Convenience: case-insensitive lookup; nil means a successful search found no match
208
217
  client.signers.find_by_email('alice@example.com')
209
218
  ```
210
219
 
@@ -381,8 +390,205 @@ rescue Assinafy::ApiError => e
381
390
  end
382
391
  ```
383
392
 
384
- Resource YARD documentation includes request/response examples and calls out known differences
385
- between the current OpenAPI document and the deployed sandbox.
393
+ The [SDK API reference](docs/API_REFERENCE.md) maps every API operation to its Ruby method and documents exact
394
+ authentication, parameters, request bodies, success responses, and every published schema property.
395
+
396
+ ## Complete document workflow
397
+
398
+ Every call below returns the SDK value after response-envelope handling. Use the
399
+ [API operation table](docs/API_REFERENCE.md#api-operations) for each method's exact HTTP authentication,
400
+ parameters, request body, and success wire response. Named response objects link to the complete
401
+ [schema catalog](docs/API_REFERENCE.md#schema-catalog).
402
+
403
+ ### 1. Configure a workspace client
404
+
405
+ ```ruby
406
+ client = Assinafy::Client.new(
407
+ api_key: ENV.fetch('ASSINAFY_API_KEY'),
408
+ account_id: ENV.fetch('ASSINAFY_ACCOUNT_ID'),
409
+ base_url: ENV.fetch('ASSINAFY_BASE_URL', 'https://api.assinafy.com.br/v1')
410
+ )
411
+ ```
412
+
413
+ Use one authentication credential per client. Account-scoped methods use the configured account unless an
414
+ explicit `account_id_override` is supplied.
415
+
416
+ ### 2. Upload a document or select a template
417
+
418
+ ```ruby
419
+ document = client.documents.upload(
420
+ { file_path: './contract.pdf' },
421
+ name: 'Customer agreement'
422
+ )
423
+ # => Document
424
+
425
+ template = client.templates.get('template-id')
426
+ # => Template
427
+ ```
428
+
429
+ For in-memory data, pass `{ buffer: pdf_bytes, file_name: 'contract.pdf' }`. Upload requests are multipart;
430
+ template-based document creation is shown after the signer is available.
431
+
432
+ ### 3. Wait for document processing
433
+
434
+ ```ruby
435
+ document = client.documents.wait_until_ready(
436
+ document.fetch('id'),
437
+ max_wait_seconds: 60,
438
+ poll_interval_seconds: 2
439
+ )
440
+ # => Document with a ready status and populated pages
441
+ ```
442
+
443
+ The helper raises `Assinafy::ValidationError` for invalid interval values and `Assinafy::Error` for a failed
444
+ terminal document status or timeout.
445
+
446
+ ### 4. Create or reuse a signer
447
+
448
+ ```ruby
449
+ signer = client.signers.find_by_email('signer@example.com') ||
450
+ client.signers.create(full_name: 'Example Signer', email: 'signer@example.com')
451
+ # => Signer
452
+ ```
453
+
454
+ ### 5. Estimate and request signatures
455
+
456
+ ```ruby
457
+ signer_request = {
458
+ id: signer.fetch('id'),
459
+ verification_method: 'Email',
460
+ notification_methods: ['Email'],
461
+ step: 1
462
+ }
463
+
464
+ cost = client.assignments.estimate_cost(
465
+ document.fetch('id'),
466
+ signers: [signer_request]
467
+ )
468
+ # => CostEstimate
469
+
470
+ assignment = client.assignments.create(
471
+ document.fetch('id'),
472
+ method: 'virtual',
473
+ signers: [signer_request],
474
+ message: 'Please review and sign.',
475
+ expires_at: '2026-12-31T23:59:00Z'
476
+ )
477
+ # => Assignment, including signing_urls
478
+ ```
479
+
480
+ The template alternative binds a signer to a template role and creates the document and assignment together:
481
+
482
+ ```ruby
483
+ role_signer = {
484
+ role_id: template.fetch('roles').first.fetch('id'),
485
+ id: signer.fetch('id'),
486
+ verification_method: 'Email',
487
+ notification_methods: ['Email']
488
+ }
489
+
490
+ template_cost = client.documents.estimate_cost_from_template(template.fetch('id'), [role_signer])
491
+ # => CostEstimate
492
+
493
+ template_document = client.documents.create_from_template(
494
+ template.fetch('id'),
495
+ [role_signer],
496
+ name: 'Customer agreement', message: 'Please review and sign.'
497
+ )
498
+ # => Document with an embedded Assignment
499
+ ```
500
+
501
+ ### 6. Complete the signer flow
502
+
503
+ Signer-facing calls use the one-time access code delivered by Assinafy. They do not use the workspace API key.
504
+
505
+ ```ruby
506
+ signing = Assinafy::Client.new(base_url: ENV.fetch('ASSINAFY_BASE_URL'))
507
+ access_code = ENV.fetch('ASSINAFY_SIGNER_ACCESS_CODE')
508
+ verification_code = ENV.fetch('ASSINAFY_VERIFICATION_CODE')
509
+
510
+ signer_data = signing.signers.self_data(signer_access_code: access_code)
511
+ signing.signers.accept_terms(signer_access_code: access_code) unless signer_data['has_accepted_terms']
512
+ signing.signers.verify_email(
513
+ verification_code: verification_code,
514
+ signer_access_code: access_code
515
+ )
516
+
517
+ signing_document = signing.assignments.signer_document(signer_access_code: access_code)
518
+ signing.signer_documents.sign_multiple(
519
+ [signing_document.fetch('id')],
520
+ signer_access_code: access_code
521
+ )
522
+ ```
523
+
524
+ The virtual assignment created above uses `sign_multiple`. For a collect assignment, submit each positioned
525
+ item through `assignments.sign`:
526
+
527
+ ```ruby
528
+ collect_items = signing_document.fetch('assignment').fetch('items').map do |item|
529
+ {
530
+ item_id: item.fetch('id'),
531
+ field_id: item.dig('field', 'id'),
532
+ page_id: item.dig('page', 'id'),
533
+ value: 'Accepted'
534
+ }
535
+ end
536
+
537
+ signing.assignments.sign(
538
+ signing_document.fetch('id'),
539
+ signing_document.dig('assignment', 'id'),
540
+ collect_items,
541
+ signer_access_code: access_code
542
+ )
543
+ ```
544
+
545
+ The SDK maps the documented snake_case item keys to the API's camelCase request keys.
546
+
547
+ `DigitalCertificate` may be supplied as an assignment verification method. Provider documentation refers to
548
+ `/signers/certificate/start` and `/signers/certificate/complete`, but their authentication and request/response
549
+ schemas are not published in the API v1 machine contract, so the SDK does not expose those completion calls.
550
+ Contact Assinafy before enabling a digital-certificate signing flow in production.
551
+
552
+ ### 7. Inspect, tag, download, and verify
553
+
554
+ ```ruby
555
+ document_id = document.fetch('id')
556
+
557
+ activities = client.documents.activities(document_id) # => Array<DocumentActivity>
558
+ tags = client.documents.append_tags(document_id, ['tag-id']) # => Array<Tag>
559
+ client.documents.replace_tags(document_id, tags.map { |tag| tag.fetch('id') })
560
+
561
+ original_pdf = client.documents.download(document_id, 'original')
562
+ signed_pdf = client.documents.download(document_id, 'pades')
563
+ certificate_pdf = client.documents.download(document_id, 'certificated')
564
+
565
+ verification = client.documents.verify('signature-hash-from-assinafy')
566
+ # => DocumentVerification; inspect verification['is_valid']
567
+ ```
568
+
569
+ `verify` returns Assinafy's verification result. It does not independently validate the PDF signature or its
570
+ certificate chain; see [Authentication and safety](docs/API_REFERENCE.md#authentication-and-safety).
571
+
572
+ ### 8. Handle errors and clean up
573
+
574
+ ```ruby
575
+ begin
576
+ client.documents.details(document_id)
577
+ rescue Assinafy::ValidationError => e
578
+ warn e.errors.inspect
579
+ rescue Assinafy::ApiError => e
580
+ warn "Assinafy returned #{e.status_code}: #{e.message}"
581
+ rescue Assinafy::NetworkError => e
582
+ warn "Network failure: #{e.message}"
583
+ end
584
+
585
+ client.documents.delete(document_id)
586
+ client.signers.delete(signer.fetch('id'))
587
+ client.templates.delete(template.fetch('id')) if template
588
+ ```
589
+
590
+ Delete resources only after downstream work is complete. Some resources can return `409` while processing or
591
+ while still referenced; retry only after resolving the reported state.
386
592
 
387
593
  ## Pagination
388
594
 
@@ -431,16 +637,26 @@ All inherit a `#context` Hash with debugging metadata.
431
637
  ## Tests
432
638
 
433
639
  ```bash
434
- bundle exec rake spec # 200+ RSpec examples, including a coverage matrix
640
+ bundle exec rake spec # 300+ RSpec examples, including a coverage matrix
435
641
  bundle exec rubocop # Linting
642
+ bundle exec steep check # Static type checking against the published RBS
436
643
  bundle exec bundler-audit check # Dependency CVEs
644
+ ruby scripts/check_api_contract.rb --file path/to/openapi.json # validate a local contract document
437
645
  ```
438
646
 
439
- The coverage spec checks the committed route-to-method matrix for duplicate operations or wrappers, missing public methods, aliases that drift, and unmapped resource methods. It does not download or compare the [OpenAPI document](https://api.assinafy.com.br/v1/docs/openapi.json) during the test run; update the matrix deliberately when the remote contract changes.
647
+ The coverage spec validates the committed route-to-method inventory, public wrappers, aliases, and resource mappings without network access.
648
+
649
+ Ordinary pull-request CI remains network-independent. A weekly scheduled job runs
650
+ `scripts/check_api_contract.rb` against the upstream document. When Assinafy publishes a new contract, update
651
+ the SDK implementation, tests, API reference, and expected fixture together.
440
652
 
441
653
  ### Live integration tests
442
654
 
443
- The suite in [`spec/integration/`](spec/integration/live_sandbox_spec.rb) exercises representative workflows across every resource against the real sandbox. It is not an exhaustive operation-by-operation contract check: OTP- or feature-gated routes may be skipped, and sandbox rollout can lag the current OpenAPI document. The suite is excluded from the default run and only executes when `ASSINAFY_LIVE=1` is set with credentials:
655
+ The suite in [`spec/integration/`](spec/integration/live_sandbox_spec.rb) exercises safe sandbox workflows across
656
+ every workspace resource. Signer-code, OTP, password, social-login, API-key mutation, signature upload, and
657
+ irreversible sign/decline calls are wire-contract tested in the default suite but are not live-automated without
658
+ their one-time credentials or explicit state changes. The live suite is excluded from the default run and only
659
+ executes when `ASSINAFY_LIVE=1` is set with credentials:
444
660
 
445
661
  ```bash
446
662
  ASSINAFY_LIVE=1 \
@@ -454,6 +670,10 @@ bundle exec rspec spec/integration
454
670
 
455
671
  > These tests create and clean up real resources and, for the assignment flow, send real signature-request emails to the addresses in `ASSINAFY_TEST_EMAIL` / `ASSINAFY_TEST_EMAIL2`.
456
672
 
673
+ GitHub Actions provides the manual **Live Sandbox** workflow. Select a protected environment containing
674
+ `ASSINAFY_API_KEY`, `ASSINAFY_ACCOUNT_ID`, `ASSINAFY_TEST_EMAIL`, and `ASSINAFY_TEST_EMAIL2`; environment
675
+ branch/tag rules and approvals are applied before GitHub exposes those secrets to the job.
676
+
457
677
  ## Contributing
458
678
 
459
679
  Pull requests and issues are welcome at <https://github.com/assinafy/ruby-sdk>.