assinafy 1.5.0 → 1.5.1

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