How a Sustainability Certification Nonprofit Automated Agreement Management with Adobe Acrobat Sign and Power Automate - CloudFronts

How a Sustainability Certification Nonprofit Automated Agreement Management with Adobe Acrobat Sign and Power Automate

01Summary

Part 1 covered the data model behind a certification Agreement Manager in Microsoft Dataverse: eleven tables, a strict ownership boundary that leaves signing to Adobe Acrobat Sign and document storage to Azure Blob, and a coverage model whose whole purpose is to let certification logic answer a question with a query instead of a judgement call.

This part covers the automation built on top of it, and it is written against the environment rather than the specification. I went back into the development org and checked, because a build note written at two in the morning is not evidence.

What is there: six cloud flows, all activated, a validation action with a plugin step behind it, Adobe configured with real workflow identifiers, and eight agreement requests that have genuinely run through the process, four of them to Fully Executed. What is not there: the custom API that creates coverage, which was never built. Nothing else is permitted to write coverage, so the coverage table is empty. The interesting part is that the system already says this about itself.

02In This Blog

What is actually deployed, verified against the environment, and what the gap between the specification and the org tells you about handing work over.

  • The Scenario: A finished data model that still needed to send agreements, track them, store evidence, and produce coverage.
  • The Approach: A decision table fixing flow against plugin against custom API for every operation, written before any automation existed.
  • The Action: Six solution aware flows, a validation action with a plugin behind it, and Adobe wired to real template workflow identifiers.
  • The Outcome: A process that runs end to end up to signature, and one clearly identified missing component that the validation data names out loud.

A half finished system is safe to inherit when it tells you what is missing. It is dangerous when you have to find out.

Table of Contents

03Where Part 1 Left Off

The foundation was eleven Dataverse tables in three layers. A configuration layer holding Agreement Type, Agreement Template Version, Agreement Role Definition and Agreement Requirement Policy. A process layer centred on Agreement Request with four parental children: Agreement Party, Agreement Recipient, Agreement Document and Agreement Validation Issue. An evidence layer of Agreement Coverage and Agreement Coverage Scope.

Adobe Acrobat Sign for Microsoft Dynamics 365 owns signature execution. Azure Blob Storage owns the binaries. Dataverse owns the business decision. Eight alternate keys hold the integrity guarantees, the strongest being the one that makes a second active coverage for the same scope impossible.

Everything below assumes that foundation, because the specification forbade starting automation until the tables, keys, forms and views were finished and validated. That sequencing held, and it is the reason the automation layer is as small as it is.

04Flow, Plugin, or Custom API

Most Power Platform builds decide implementation style per developer preference, which produces a system where nobody can predict where a given behaviour lives. This build used one canonical decision table, written before any automation existed.

OperationPreferred implementationEscalate to code when
Validate Agreement RequestSolution aware flow or command action that clears unresolved issues, writes new ones, and updates the validation summaryThe gate needs synchronous reuse, in which case it is promoted to a custom API
Send for SignaturePower Automate using the Adobe connector, only after validation leaves no blockerMultiple callers need one stable callable action
Adobe status syncAdobe managed app plus a lightweight sync flow where neededConcurrency or idempotency require server side code
Retrieve and store documentsPower Automate for small files, Azure Function for large onesBlob SDK, managed identity, hashing or retry handling are awkward in a flow
Create CoverageCustom API, mandatory. A pure flow is explicitly not acceptableAlways, because supersession must be transaction safe
Open final documentFlow or Azure Function generating a short lived linkManaged identity and key vault access are easier in a function
Coverage gateSynchronous plugin or custom APICertificate issuance must be blocked immediately rather than eventually

The canonical matrix. Deviating from it required a written justification, not a preference.

The three custom APIs

  • Validate Agreement RequestRe runs validation, populates the policy reference date if blank, enforces legal review approval where the agreement type demands it, upserts validation issues by alternate key, and returns a summary with blocker and warning counts.
  • Create Agreement CoverageResolves policy and scope, builds the coverage match key, supersedes the prior active row and inserts the new one in a single transaction, creates the scope rows, and moves the request to Coverage Created.
  • Evaluate Coverage GateGiven an account, legal entity, agreement type code, scope context and reference date, returns pass, block or exception with the reason and the coverage identifier that satisfied it.

05What Is Built, and What Comes Next

It is worth being precise here, because a diagram of the full design would overstate what runs. The following is what the environment reports, not what the specification asked for.

Figure 1: What is running today, and what is designed and waiting
Figure 1: What is running today, and what is designed and waiting

The automation, as deployed

ComponentWhat it doesState in the environment
Validate Agreement RequestWrites a structured issue row for every problem found on a requestCustom action and plugin step, both activated
FLW-01 Send for SignatureHands the prepared agreement to AdobeActivated
FLW-02 Adobe Status SyncBrings signing progress back onto the request and recipientsActivated
FLW-03 Document RetrievalFetches the signed file and records its metadataActivated, but no storage account configured
FLW-04 Manual Upload ApprovalHandles the wet signature exception pathActivated
FLW-06 Cancellation and SupersessionCloses or replaces a request properlyActivated
FLW-08 Coverage HealthExpires coverage that has passed its dateActivated, nothing to act on yet
Create Agreement CoverageTurns a completed signature into the record the business readsNot built

Two flows from the specification, the reminder engine and the legacy sync, were never built and are not missed

Validation writes rows, not paragraphs

The validation operation does three things in order. It clears unresolved issues from the previous run, writes the current findings as structured Agreement Validation Issue records, and rolls those up into a read only summary field on the request. Each issue carries a code, a severity, a source operation and a resolved flag.

Because issues are upserted through the alternate key on request plus issue code plus source operation, running validation five times produces five evaluations and not five copies of the same finding. Codes such as LEGAL_REVIEW_PENDING, POLICY_NO_MATCH, CONFIG_CONFLICT and DOC_INTEGRITY_ERROR are meaningful to both a person reading the form and a flow deciding whether to proceed.

The gap, and why it is visible

Coverage is the record everything downstream trusts, so the design allows exactly one component to write it: a custom API that performs supersession and insertion in a single transaction. That component does not exist in the environment. There are no custom APIs registered in the org at all.

The consequence is visible in two places at once. Four agreement requests have reached Fully Executed, and the coverage table contains nothing. Four rows in the validation issue table carry the code COVERAGE_API_MISSING. The system is not silently broken, it is loudly incomplete, and those are very different things to inherit.

This is the argument for the restriction rather than against it. Because only one sanctioned writer was ever allowed, the absence of that writer produced an empty table and an explicit issue code. Had several components been permitted to write coverage directly, some of them would have run, and the table would now hold a partial, plausible looking set of rows that nobody could trust.

Figure 2: The send sequence. Only the first two steps exist today.
Figure 2: The send sequence. Only the first two steps exist today.

The same reasoning applies to the enforcement that goes with it. Security roles cannot express this rule, because the integration account legitimately needs write access for other purposes. A check inside a flow cannot either, because somebody can always build another flow. Checking who is calling fails, because an administrator is a legitimate user. Only checking that a write arrived through the sanctioned operation holds, and that check has to be in place before the first coverage row exists rather than after.

06The Coverage Gate

Everything above exists to make one downstream check trustworthy. At four points in the certification lifecycle, the process asks whether valid coverage exists, and it asks only the coverage tables.

TimingRequired behaviour
Application acceptedCheck whether the legal entity has active coverage satisfying the active requirement policy. If it is missing or outdated, create an Agreement Request.
During assessmentDisplay the agreement requirement status: valid coverage, agreement sent, awaiting signature, missing or outdated, or legal review.
Certification decisionBlock approval or certificate issuance where valid coverage is absent, unless an approved exception exists.
New mandatory versionExisting coverage may become outdated based on the requirement policy mandatory dates, without any change to the coverage record itself.

The gate reads coverage. It never reads Adobe status, legacy CRM status, or the presence of a PDF.

What was handed over rather than forced

  • The coverage API is not built. Four requests are fully executed and the coverage table is empty. This is the one substantial piece of work remaining, and it is the piece the whole design exists to protect, so it should be built as the transactional custom API rather than as a convenient flow.
  • A validation issue alternate key is still in failed state. Repeated rows for the same request and code prevent the unique index from building. Clearing them is a data decision, so it was handed over with the counts rather than actioned.
  • Blob storage is half configured. The container name and path prefix are set as environment variables. The storage account name and the function URL are empty, so document retrieval has somewhere to put files in principle and nowhere in practice.
  • Two of four template versions have no Adobe workflow identifier. The two certification templates are fully wired. The NDA and data processing templates are not, and would fail on send.
  • Administrative form security. Dataverse exposes no association between forms and security roles through the Web API, so this stays a manual step in the form designer.

07Impact

  • 6Cloud flows, all activated
  • 8Requests run through the process
  • 4Reached Fully Executed
  • 0Coverage rows, and the data says why
  • The process runs. Eight requests have gone through preparation, validation and signature, and four have reached Fully Executed against real Adobe templates.
  • Validation became data. Structured issue rows with codes drive views, gate the send, and can be counted. Twenty four of them currently describe the exact state of the build.
  • The remaining work is one named component, not a vague list. Anyone picking this up knows what to build and where it plugs in.
  • Nothing is owned by a person. All six flows are solution aware, so the whole thing promotes between environments as one unit.
  • The gaps are legible. A missing storage account, two unwired templates and an unbuilt API are all discoverable in minutes rather than after a failed go live.

08Conclusion

The automation layer is small in proportion to what it controls, and that is a consequence of the data model rather than an accident. Because validation issues are rows with an upsert key, no logic is needed to avoid duplicating them. Because coverage carries a deterministic match key and a conditional active key, the platform itself refuses a second active row. Because Adobe owns signing and Blob owns the binaries, there is no provider abstraction and no file handling code to maintain.

The part worth copying is not a technique, it is a habit. I went back into the environment before writing this and found that several things I believed were true were not. The send flow I remembered leaving in draft is activated. The Adobe identifiers I remembered as placeholders are real. The coverage API I described as designed is genuinely absent, and the system had been saying so all along in a table anyone could query.

Build systems that report their own state, then read what they tell you instead of trusting your notes. A structured validation table costs very little and turns the question of what is left to do from an argument into a query.

09FAQ

01Why is coverage creation a custom API rather than a Power Automate flow?

Because supersession and insertion must be atomic. A flow performs supersede, clear key, link, and insert as separate calls, so a failure part way through leaves the legal entity with no coverage or with two. The custom API does all of it in one transaction, which is why the specification made it mandatory rather than recommended.

02Why check an execution context marker instead of using security roles?

Because the accounts you would need to block are accounts that legitimately hold write access for other reasons. Security roles answer who is calling. The marker answers how they are calling. Only the second question distinguishes the sanctioned API from a direct import performed by the same identity.

03Why upsert validation issues by alternate key?

Validation runs many times on the same request. Without an upsert key on request plus issue code plus source operation, each run appends another copy of every unresolved finding, and within a week the issue list is unusable. With it, a repeated finding updates in place and the count stays meaningful.

04Why compute a SHA-256 hash if the file came straight from Adobe?

Because retrieval and upload are two network operations, and the evidential value of the document depends on it being provably the same bytes that were signed. The hash is computed server side on the retrieved bytes and persisted, and a mismatch sets an integrity error status and raises a structured issue rather than storing a suspect file silently.

05Can the same design work with a different signing provider?

Yes, and it was scoped so that provider specific fields stay isolated for exactly that reason. What was explicitly rejected was building a generic multi provider framework up front, because that cost is real and immediate while the second provider is hypothetical.

06What has to be true before the gate can be switched on?

Historical executed agreements must be backfilled into coverage, with a reconciliation report covering source count, migrated coverage count, missing files, exceptions and any duplicate active coverage. Enforcing before backfill blocks legitimate certifications and destroys confidence in the system in its first week.

10Get in Touch

Building automation that has to be provably correct?

If a compliance decision in your organisation depends on an automated step being impossible to bypass, we can help you design the flow, plugin and custom API split so that the invariant holds even against a privileged mistake.

Talk to us →
Suchit Chaudhari

Suchit Chaudhari

Dynamics 365 and Power Platform Consultant · CloudFronts

Works across Dynamics 365 and the Power Platform, with a focus on turning manual, document driven business processes into governed Dataverse solutions that survive audit and scale past their first release.


Share Story :

SEARCH BLOGS :

FOLLOW CLOUDFRONTS BLOG :


Categories

Secured By miniOrange