From 7edbe081c2f0a608f5564468c1321393b154c046 Mon Sep 17 00:00:00 2001 From: Shawn Jackson Date: Fri, 25 Sep 2026 12:59:55 -0700 Subject: [PATCH] Adding more to docs --- docs/how-tos/setup-department.md | 6 + docs/reference/workflow-variables.md | 27 ++++ docs/web-app/data-protection.md | 7 +- docs/web-app/help-setup.md | 92 ++++++++--- docs/web-app/protected-workflows.md | 220 +++++++++++++++++++++++++++ docs/web-app/workflows.md | 19 +++ 6 files changed, 352 insertions(+), 19 deletions(-) create mode 100644 docs/web-app/protected-workflows.md diff --git a/docs/how-tos/setup-department.md b/docs/how-tos/setup-department.md index 150afd6..187f7f0 100644 --- a/docs/how-tos/setup-department.md +++ b/docs/how-tos/setup-department.md @@ -10,6 +10,12 @@ This page is the generic, screen-by-screen walk-through. For concrete values — This guide walks you through every step needed to get your Resgrid department configured and ready for day-to-day use. Work through the sections in order — each one builds on the previous. +## Use Setup Wizard and Setup Report + +When enabled for your deployment, department administrators can open **Setup Wizard** from the Department or Help menu. Choose your operating profile and areas to use now, learn about every product area and optional add-on, then follow links to the existing setup screens. Use **Setup Report** to verify fresh evidence after saving. Personal learning choices and feature interest never purchase, enable or send anything. + +Setup Report replaces the old setup score with verified, failed and unknown checks. Missing evidence is not a pass. Setup and the deterministic Admin Assist do not require an AI add-on. See [Help & Setup](../web-app/help-setup) for resumable progress, worklist review and add-on prerequisites. The examples below require local review; they are not approved staffing, clinical or response policy. + ## Before You Begin Collect the following information so you have it handy as you configure each section: diff --git a/docs/reference/workflow-variables.md b/docs/reference/workflow-variables.md index d8b57a7..128a4a1 100644 --- a/docs/reference/workflow-variables.md +++ b/docs/reference/workflow-variables.md @@ -42,6 +42,28 @@ These variables are available in **every** workflow regardless of trigger event | `{{ timestamp.time }}` | string | Current time (department TZ) as `HH:mm:ss` or `hh:mm tt` | | `{{ timestamp.day_of_week }}` | string | Day name (e.g., "Monday") | +### Run Variables + +Set for every step of every run. + +| Variable | Type | Description | +|----------|------|-------------| +| `{{ run.id }}` | string | Workflow run ID | +| `{{ run.attempt }}` | int | Attempt number (1 on the first try) | +| `{{ run.idempotency_key }}` | string | 32 hex characters, the same on every retry of this step's delivery and different for every event. Use it as an `Idempotency-Key`, a FHIR identifier or HL7 `MSH-10` so a retried delivery isn't recorded twice | + +### Template Helpers + +Available in every workflow template. Pipe a value into them. + +| Helper | Example | Result | +|--------|---------|--------| +| `json_escape` | `"{{ call.notes \| json_escape }}"` | The value escaped for use inside a JSON string (quotes, backslashes, control characters, `<`, `>`, `&`) | +| `xml_escape` | `{{ call.notes \| xml_escape }}` | The value escaped for XML text or attributes; characters XML can't carry are dropped | +| `hl7_escape` | `OBX\|1\|TX\|NOTE\|\|{{ call.notes \| hl7_escape }}` | HL7 v2 escaping of `\| ^ ~ \ &`, with CR and LF turned into `\X0D\` and `\X0A\` | +| `fhir_datetime` | `{{ call.closed_on \| fhir_datetime }}` | `2026-09-24T14:05:00Z` (UTC) | +| `hl7_ts` | `{{ call.closed_on \| hl7_ts }}` | `20260924140500+0000` (UTC) | + ### User Variables (Triggering User) Populated from the user who triggered the event. If no specific user is associated with the event (e.g., Unit Added, Shift Created), these variables are empty/null. @@ -98,6 +120,11 @@ Populated from the user who triggered the event. If no specific user is associat | `{{ call.form_data }}` | string | Custom form data | | `{{ call.is_deleted }}` | bool | Whether the call is deleted | | `{{ call.deleted_reason }}` | string | Deletion reason | +| `{{ call.part2_consent_on_file }}` | bool | 42 CFR Part 2 consent (or another Part 2 basis) is on file for the call. Never protected, so conditions can use it | + +:::note Advanced Data Protection +In a department with [Advanced Data Protection](../web-app/data-protection), the protected call fields above (name, nature, notes, address, geolocation, type, incident, reference and external numbers, completion notes, contact name and number, what3words, form data, deletion reason) render as `REDACTED`. A workflow with an approved [Protected Workflow](../web-app/protected-workflows) release also gets the fields it was approved for under `protected.call.*` (for example `protected.call.completed_notes`, `protected.call.form` with the form data parsed, `protected.call.subject_ids.` for the call's subject identifiers, and `protected.call.udf.` for released call custom fields), in its output template only. Subject identifiers are never available under `call.*`. Anywhere else, `protected.*` renders as an empty string. +::: #### Call Collection Variables diff --git a/docs/web-app/data-protection.md b/docs/web-app/data-protection.md index e75768f..03f5544 100644 --- a/docs/web-app/data-protection.md +++ b/docs/web-app/data-protection.md @@ -25,7 +25,7 @@ ADP is one control inside a HIPAA / privacy / ePCR compliance program that your - **Search, reporting, exports, integrations and offline access** cannot see protected content (Records narrative search is withdrawn; export columns are written as `REDACTED` unless an egress acknowledgement is recorded). - **Big Board** shows a reduced *protected incident* shell instead of call details. -- **Workflows** receive redacted payloads. +- **Workflows** receive redacted payloads by default. Administrators can approve a specific [Protected Workflow](protected-workflows) to send selected fields to one pinned HTTPS destination; every such disclosure is recorded in a hash-chained audit log. - **Text, email, push and voice notifications** send generic content by default (*"A protected dispatch is available — sign in to Resgrid"*). Relaxing a channel is a separate, acknowledged policy change. - Resgrid support cannot read protected values without an explicit, audited, department-approved support grant. Key loss is recoverable only through the documented recovery process. @@ -54,6 +54,10 @@ The page then shows **migration progress** (rows processed, current table, anoma Cancel the add-on on the subscription page. Protection stays active until the end of the current billing period, then an overnight migration decrypts the data back to standard storage (**offboarding**). Until that date the managing member can **revoke offboarding**. Re-enabling later requires purchasing the add-on again and a new enrollment. +## Protected Workflows + +The **Protected Workflows** section of this page turns on the one deliberate exception to workflow redaction. It needs a versioned warning acknowledgement, a fresh verification, and the **Configure Protected Data Delivery** permission. You can also require a second administrator to approve each protected workflow. See [Protected Workflows](protected-workflows) for setup, the approval rules and a Dataverse example. + ## Emergency contacts The Data Protection page also hosts the member's **emergency contacts** for this department (name, relationship, phone, alternate phone, email, notes, primary flag), stored under the department's protection settings. @@ -76,4 +80,5 @@ The Data Protection page also hosts the member's **emergency contacts** for this | Permissions | `ManageDepartmentDataProtection` (31), `ViewProtectedCallData` (32), `EditProtectedCallData` (33), `ViewProtectedPersonnelData` (34), `ViewProtectedContactData` (35), `ViewProtectedOperationalData` (36), `ExportProtectedData` (37), `ConfigureProtectedDataEgress` (38), `BreakGlassProtectedData` (39) | | State | `DepartmentDataProtectionPolicies.State` (durable); migration and offboarding run by workers in the department's window | | Grant | `IProtectedGrantContext` / `__ResgridProtectedGrant` form field carries the step-up grant on writes | +| Protected Workflows | Toggle and two-person rule on `DepartmentProtectedDataEgressPolicies`; releases and the disclosure chain in `WorkflowProtectedReleases` / `ProtectedWorkflowDisclosures`; broker workload purpose `protected-workflow` | | Design | `int-Coordination/docs/architecture/department-protected-data-implementation-plan.md` | diff --git a/docs/web-app/help-setup.md b/docs/web-app/help-setup.md index 7ff8463..13fd404 100644 --- a/docs/web-app/help-setup.md +++ b/docs/web-app/help-setup.md @@ -3,30 +3,86 @@ sidebar_position: 53 title: Help & Setup --- -# Help & Setup +# Setup Wizard, Setup Report and Admin Assist -The Help module provides onboarding assistance and department configuration evaluation. + +## Start or resume setup -![Setup report](/img/web-app/home/setup-report.png) +Department administrators can open **Setup Wizard** from the Department or Help menu when setup is enabled for the deployment. An unfinished setup also appears on the dashboard. Dismissing that prompt affects only your account; the menu remains available. Setup Wizard and Setup Report are available independently of Admin Assist and do not require an AI add-on. -## Dashboard Tutorial +Choose **Fresh setup**, **Review existing setup**, or **Import or migration**. The choice records your intent; it does not import or overwrite data. Use the department operating profile to describe your organization and link approved local policies. Fire, EMS, mental health, SAR, emergency response, Hazmat, industrial, security and mutual-aid packs suggest areas to review. They do not establish qualifications or authorize clinical, tactical or hazardous work. -The `DashboardTutorial` action returns a partial view overlay that guides new users through the dashboard interface. +The nine-step journey covers goals and applications, the department profile, all-area orientation, people and access, operational essentials, selected workflows, add-ons, verification and practice, and review and handover. Pause and resume without creating sample incidents or overwriting existing configuration. -## Setup Report +Selected operating packs suggest areas without changing your choices. Site references use existing department group IDs; policy references use existing, unexpired department document IDs. Saving checks those references and rejects concurrent overwrites. A valid reference does not prove that its contents are approved or sufficient. Verification checks whether linked policies or site groups later become unavailable, and flags documents scheduled for removal within 30 days. A reviewed profile without a continuity link receives an administrative review prompt; an external procedure may still exist. The checks do not read or certify policy contents. -The `SetupReport` action generates a department configuration completeness evaluation: +An optional **Expected email polling interval** compares recorded mailbox polls with your declared maximum interval. Leave it blank when no interval has been established. Missing or future poll timestamps remain unknown, and low call volume alone does not imply failure. SMS, CAD push and API intake require separate telemetry; the expectation does not change polling or send a test. -1. Calls `GetDepartmentSetupReportAsync` to analyze the department's configuration -2. Generates a numerical score via `GenerateSetupScore` -3. Displays the report with recommendations + +## Choose the areas your department uses -The setup report evaluates: -- Whether core settings are configured -- Whether groups/stations are created -- Whether units are defined -- Whether call types and priorities are set -- Whether notification rules are configured -- Other configuration completeness metrics +Mark each area **Use now**, **Learn later**, or **Not applicable**. A not-applicable choice requires a reason: outside the mission, managed in another approved system, managed by a responsible partner, or no current need. Baseline security remains in scope. Explore Resgrid covers applications, calls, people, units and location, communication, contacts and site knowledge, records, maintenance, inventory, deployments and business, automation, security and plans. Each feature explains its purpose, value, example and adoption requirements. -This helps new departments ensure they've properly configured all necessary system components before going operational. +Area choices are shared by department administrators. Learning and interest choices are personal. Another administrator's save can require you to reload and review before saving again. Learning a feature does not configure it, purchase an add-on, enable a module or send anything. + + +## Configure and verify + +Use **Open owning screen** to configure a feature with its existing permissions and validation. Some settings include contextual help and a highlighted field when opened from Admin Assist. Save on that screen, then use **Return to setup and verify**. The return link does not submit the form. Its navigation context lasts 30 minutes and is tied to the current administrator and department, so it can survive the owning screen’s save and redirect. Returning to setup clears it. Choose **Verify again** after saving. + +The report separates verified checks, failures and unknown evidence. Unpurchased optional add-ons do not reduce core completion. Deferring an area does not hide a verified critical failure or uncertainty in an active critical check. Selected areas with no automated checks are listed explicitly; they are not verified. Missing, restricted or unavailable source data remains unknown; it is never treated as zero. Critical unknowns prevent a fully verified result. Review the evidence time and refresh again if configuration changed during the read. + +Record an administrative review to retain the report version, evidence revision, selected-scope revision, timestamp and unresolved-check counts. A changed configuration, scope or catalog makes that review visibly out of date. Each newly added administrator still has a separate orientation checklist. An optional revisit date is stored in the report; it does not schedule a one-off notification. Weekly follow-up is a separate preference. + +Setup Report replaces the old numerical setup score. It is administrative configuration guidance, not certification of operational readiness or proof that a page was delivered. Start a Communication Test explicitly through its own screen when communication verification is needed. + +**Open a fresh printable report** rechecks access and reloads the summary. It includes current findings, selected areas, personal feature interests and public add-on guidance. It omits protected notes, names, source records and credentials. Printing or saving a local copy does not create a managed server export; handle the copy under department policy. + + +## Understand optional add-ons + +| Add-on | What it adds | Adoption considerations | +|---|---|---| +| Push-to-Talk | Voice channels in supported Resgrid clients | Review seats, devices and channel membership. Phone voice alerts and radio requirements are separate. | +| Advanced Data Protection | Additional protection and scoped disclosure for supported sensitive content | Purchase and enrollment are separate. Review MFA, recovery, supported fields and effects on search, exports and integrations. | +| Readiness Pro | Maintenance, work orders, preventive and corrective work, approvals and safety holds | Checklists remain available without the add-on. Maintenance also needs its module and permissions. | +| Business Operations | Invoicing, rates, contracts, bids, reimbursement and workforce costing | Certifications and Deployment Finance remain available without the add-on. Pay-data reporting also requires enabled ADP; payment providers need separate setup. | +| Enhanced AI | Planned summaries, drafts, knowledge assistance and optional conversation | Availability depends on release and rollout. Deterministic setup and Admin Assist do not require it. | + +Availability can depend on subscription, rollout, module settings, permissions, protection enrollment and source availability. An unknown subscription is not confirmation of a free or paid plan. Only the managing member can perform subscription changes through the billing screen. Feature interest does not start a trial or purchase. + +The add-on step opens a comparison of all five add-ons. Each card lists its related features. Mark a feature **Interested in this feature** to see it in Setup Report with its current prerequisites and next available action. **I understand this feature**, availability, purchased entitlement, configuration and verified checks are separate states. Buying an add-on does not configure or verify the feature. The feature-setup section distinguishes recorded configuration, supported check results, current entitlement and optional opportunities. Initial evidence mappings cover personnel, groups, units, run cards, check-in timers, weather zones and email intake. Other feature configuration remains unassessed; absence of setup evidence does not prove a feature is unused. + + +## Follow up on findings + +Admin Assist adds an administrative worklist. Claim an unresolved finding, set a review date, start a review, or record an accepted exception with a reason and expiry. Exceptions do not make the underlying check pass. Fresh verification resolves a finding; a later verified failure reopens it. An evidence outage cannot resolve it. Fix records, qualifications, inventory and other source tasks through their owning screens. + +Weekly follow-up is opt-in and only runs when the deployment enables scheduled digests. Quiet hours use the department time zone. The message contains a generic link to Admin Assist; open the authenticated page to see current evidence. A notification handoff is not confirmation of delivery. Turning the preference off suppresses future handoffs. + + +## Use Settings Reference and Change History + +Settings Reference searches a release-pinned public documentation catalog. It does not search department records or attachments. If your language has no reference article, the result labels its source language. + +Supported scalar settings offer **Preview a proposed value**. A preview compares values in memory, shows related health-check changes and lists its limits. It does not save configuration. Current operational previews cover v4 map-marker selection, the automatic-availability status projection and administrator MFA enrollment. These do not establish every client's behavior, actual physical availability or session/recovery readiness. + +**Preview plan capacity** compares proposed total personnel and unit counts with observed base-plan headroom. It does not purchase capacity or add resources; add-on seats, provider quotas and pricing are separate. + +**Preview dispatch routing** uses a saved call ID as a route scenario. Choose an explicit UTC roster time within seven days of now and all three proposed shift/crew/group options. It compares direct, group, role and unit crew/group recipients with the broadcaster's resolver, preserving direct duplicates and empty-shift fallback. Current membership and crews are not reconstructed historical assignments. Unit devices, printers, channel eligibility, provider handoff and delivery are outside these personnel counts. The preview sends nothing and rereads inputs to detect changes during evaluation. + +**Preview a permission change** compares current members through the supported action or resource visibility gate. It includes roles, department and group administration, target-group ancestors, and ungrouped targets. It counts allowed actor/target pairs separately from members, so a narrowed group scope is visible even when the same members retain some access. Existing sessions, protected fields and other permission gates still require verification. + +**Preview a module switch** compares web navigation and bounded primary-table counts. Hiding an entry keeps the underlying data; it does not prove API access is revoked or a worker stops. Mapping, Reports, Logs/Records and Inventory data totals remain unknown where a complete adapter is unavailable. Licensed maintenance, checklists and business switches require separate entitlement-aware previews. + +**Preview a text sender scenario** compares a test number against current source patterns and the complete proposed call/command switches. Choose the provider path actually in use. The result returns a masked reference and distinguishes routing branches from actual successful acceptance. SignalWire and Twilio legacy paths share their production routing decisions with the preview, but do not consult the switches uniformly. Chatbot and master-number paths, number ownership, active SMS department, plan access, webhook authentication, verified identity, opt-out exceptions and parser success require separate verification. A missing dispatch-source pattern is not proof that verified members cannot use commands. The preview never receives or sends a text. + +**Preview notification volume** compares the current staffing-suppression setting with a proposed toggle for a declared department-wide scenario. Set the future window and assumed events per member across that entire window. The result separates current membership, preference/contact gates, confirmed suppression, unknown profiles/staffing and possible channel-handoff ranges. It uses no historical event sample and does not resolve every notification-rule audience. Address/device validity, provider behavior, chat/voice, SMS segments and delivery remain separate. Changing this preview never saves settings, sends a notification or changes staffing. + +**Preview sign-in and session policy** compares department-wide MFA, SSO-only policy, password age/minimum length, idle timeout or concurrent-session limits. Enrollment is distinct from verified factors and recovery. The SSO-only gate keeps its existing safety valve when no provider is enabled; an enabled provider is not a successful login test. Session estimates respect the host policy date and distinguish next-session limits from revocation. Password age uses the owning boundary and preserves its handling of untracked dates. No password, recovery secret, session ticket, IP address or provider credential is read by these projections, and the preview does not change credentials or sessions. + +**Preview retention policy** compares a prospective Records default using a bounded sample of metadata. A blank default uses the system class default; zero means permanent. Historical policy and definition overrides remain in force, so older revisions do not simply inherit a shortened default. Known parent/preservation holds and permanent-content obligations are excluded; uncertain historical holds are identified. Remaining candidates need the owning lifecycle checks for children, disclosure copies, attachments, external storage, evidence, submissions, workflows and search erasure. These counts are not permission or proof of eligibility to purge. The preview does not read record bodies, purge content, change protection or cancel an add-on. + +Counts and checks are only as current as their source evidence. Unquantified effects still need review on the owning screen. Refresh after a configuration conflict and verify again after an actual save. Reference results can expand the full release-pinned source text and identify its source language. + +Change History starts when collection is enabled. Earlier changes are unavailable. History shows safe configuration differences; secrets and sensitive strings use presence/change markers. It does not expose another administrator's personal learning choices. diff --git a/docs/web-app/protected-workflows.md b/docs/web-app/protected-workflows.md new file mode 100644 index 0000000..aabee3d --- /dev/null +++ b/docs/web-app/protected-workflows.md @@ -0,0 +1,220 @@ +--- +sidebar_position: 48.5 +title: Protected Workflows +--- + +# Protected Workflows (ADP) + +With [Advanced Data Protection](data-protection) enabled, every [workflow](workflows) gets **`REDACTED`** in place of protected values. That stays true for any workflow you don't explicitly approve. + +A **Protected Workflow** is an exception you approve. One specific workflow may send a selected set of protected call fields to **one pinned HTTPS destination**, using **one pinned credential**. Every send, whatever its outcome, is recorded in a tamper-evident **disclosure log**. + +Typical use: a behavioral health agency writes crisis-call outcomes back to its own case system when a call closes, for example the completion notes and the call form written to a Microsoft Dynamics 365 / Dataverse case record. + +:::caution Read before enabling +Protected Workflows send decrypted protected data, which may include protected health information, to external systems you configure. Resgrid cannot control how the receiving system stores or uses this data. Only enable a workflow if the recipient is your organization or a party covered by a business associate agreement with your organization, and only release the fields the recipient needs. +::: + +## Before you start + +- ADP must be **Enabled** (or Rotating) for your department. +- You need the **Configure Protected Data Delivery** permission. By default only department administrators have it (see [Security & Permissions](security-permissions)). +- Your account needs an [authenticator app](account-security). Turning the feature on or off, approving and renewing all require a fresh verification. + +## 1. Turn on Protected Workflows + +Go to **Department dropdown → Security & Permissions → Data Protection → Protected Workflows**. + +1. Tick **Enable Protected Workflows**. +2. Read the warning and tick the acknowledgement. It is recorded with its version. +3. Optionally tick **Require a second administrator to approve protected workflows** to turn on the two-person rule. +4. Click **Save Protected Workflow settings** and verify your second factor. + +Turning the feature off suspends every protected workflow in the department immediately. + +## 2. Build the workflow + +A protected workflow can contain only **API POST** or **API PUT** steps: + +- Every step sends to the **same `https://` host**. The host must be written out in the URL, not built from a template. The path and query may still use ordinary `call.*` values such as `{{ call.number }}`. +- Every step uses the **same credential**, of type **HTTP Bearer**, **HTTP API Key** or **OAuth2 Client Credentials**. HTTP Basic is off unless your Resgrid operator enables it. +- Only **Call Added**, **Call Updated** and **Call Closed** triggers are supported in this version. + +In the **output template**, released values appear under `protected.call.*`, using the same names as [`call.*`](../reference/workflow-variables): + +| Field | Template variable | +|---|---| +| Completion notes | `protected.call.completed_notes` | +| Call form data (raw JSON) | `protected.call.form_data` | +| Call form data (parsed) | `protected.call.form`, e.g. `protected.call.form.outcome` | +| Nature, notes, address | `protected.call.nature`, `protected.call.notes`, `protected.call.address` | +| Contact name and number | `protected.call.contact_name`, `protected.call.contact_number` | +| Name, type, geolocation, what3words | `protected.call.name`, `protected.call.type`, `protected.call.geo_location`, `protected.call.w3w` | +| Incident, reference, external, source identifiers | `protected.call.incident_number`, `protected.call.reference_number`, `protected.call.external_id`, `protected.call.source_identifier` | +| Deletion reason | `protected.call.deleted_reason` | +| Subject identifiers | `protected.call.subject_ids.`, e.g. `protected.call.subject_ids.ehr_client_id` | +| Call custom fields | `protected.call.udf.`, e.g. `protected.call.udf.disposition` | + +`call.*` keeps showing `REDACTED`. A field you did not release is simply absent from `protected.call` and renders as an empty string. + +`protected.*` is **not** available in step conditions, URLs or headers, and the editor refuses to save a step that uses it there. This means plaintext can never end up in a URL or decide which way a workflow branches. In a workflow with no protected release, `protected.*` renders as an empty string and the editor flags it when you save. + +Pass every protected value through an escape helper, so that a quote or line break in free text can't break the payload's structure: + +| Helper | Use it for | +|---|---| +| `json_escape` | a value inside a JSON string: `"{{ protected.call.completed_notes \| json_escape }}"` | +| `xml_escape` | XML or SOAP text and attributes | +| `hl7_escape` | HL7 v2 fields: escapes `\| ^ ~ \ &` and turns line breaks into `\X0D\` / `\X0A\` | +| `fhir_datetime`, `hl7_ts` | a date as a FHIR `dateTime` or an HL7 `TS`, in UTC | + +The editor warns (but doesn't block) when a protected value is placed without one of them. The helpers are available in every workflow, not only protected ones. + +### Delivery options + +An API step in a protected workflow has extra **Protected delivery options**. All of them are part of the approval, so changing any of them sends the release back for approval. + +- **Content type.** One of `application/json`, `application/fhir+json`, `application/xml`, `text/xml`, `application/soap+xml`, `x-application/hl7-v2+er7` or `text/plain`. The payload is checked before it's sent: JSON must parse, a FHIR body needs a `resourceType`, XML must be well formed (DTDs are refused), and HL7 v2 must start with `MSH|^~\&` and have valid segment IDs. A payload that fails is never sent. The log records the failed rule and position, never the content. +- **Success rule.** How the destination confirms it accepted the payload: any 2xx (the default), a JSON path or XPath that must equal a value, an HL7 acknowledgement (`MSA-1` must be `AA` or `CA`), or no FHIR `OperationOutcome` error. +- **Save response values.** Up to 5 values from the response, saved **encrypted** into the call's subject identifiers, for example the EHR's new encounter ID. Sources: `json_path`, `xpath`, `hl7_field` (such as `MSA-2`), `header` (such as `Location`), and `fhir_location_id` (the ID of the created resource). The run log shows only the keys written, as `captured=[ehr_encounter_id]`. A step that saves response values needs a release that includes the subject identifiers. +- **Idempotency header** and **FHIR If-None-Exist.** `run.idempotency_key` is the same on every retry of a delivery and different for every event. Send it as a header such as `Idempotency-Key`, use it in a FHIR conditional create, or put it in HL7 `MSH-10`, so a retried delivery doesn't create a duplicate record. + +The response body is read only when a success rule or saved value needs it, and never beyond 1 MB. + +## 3. Approve the protected release + +Open the workflow. The **Protected release** panel appears below the steps. + +1. **Fields to release.** Nothing is ticked by default. Tick only what the recipient needs: + - call fields, including **Subject identifiers (every key)**; + - or individual subject identifier **keys**, such as `ehr_client_id` (you can't tick both the whole field and some of its keys); + - individual **call custom fields**. Each one is decrypted on its own, so only the ticked fields are ever opened. +2. **Destination.** Shows the pinned host, the credential and, for OAuth2, the token host, all read from your steps. It also lists anything that blocks protection, such as a non-API step, two different hosts or an `http://` URL. +3. **Recipient.** Choose **Covered entity** or **Business associate**, then enter the recipient's name and the purpose of the disclosure. +4. **Attestation.** Tick the attestation for the current warning version. Two more attestations appear when you release a custom field tagged **Restricted** or **42 CFR Part 2** (see below). The approver makes them too. +5. Click **Approve and activate**. With the two-person rule on, the button reads **Request approval**, and a *different* administrator opens the same panel and clicks **Approve** after their own verification. Whoever requested a release can never approve it. + +### Restricted and 42 CFR Part 2 fields + +In **Custom Fields → Call**, each field has a **Protected Workflows release sensitivity**: + +- **None**: no extra step. +- **Restricted**: releasing it needs the attestation that the recipient is authorized to receive restricted fields. +- **42 CFR Part 2**: releasing it needs the Part 2 redisclosure attestation, and a call is only sent when **Part 2 consent on file** is set on it (a checkbox on the call, also settable through the API). Otherwise the step is blocked with the outcome `blocked_consent` and nothing is decrypted. + +Changing a field's sensitivity, disabling it or removing it sends every release that uses it back for approval. + +**Send test with sample data** sends synthetic values, never real data, through the same protected path to the pinned host. Nothing is decrypted, and the attempt appears in the disclosure log labelled **test**. + +## What happens on every send + +Each attempt, including every retry, checks everything again from scratch: + +- ADP is Enabled and the department toggle is on. +- The release is Active and has not expired. +- The workflow is **exactly** the configuration that was approved. +- The step is POST or PUT and uses the pinned credential. +- The URL, as rendered, is `https://` on the pinned host. + +If any check fails, nothing is sent and the attempt is logged with a `blocked_*` outcome. + +Before the request leaves, an **attempted** record is written to the disclosure log. If it can't be written, nothing is sent. + +When the checks pass, Resgrid decrypts **only** the released fields for that one call, renders the payload, and makes sure no ciphertext is left in it. It then sends the request: + +- Redirects are never followed; a 3xx response counts as a blocked host. +- TLS 1.2 or later is required. +- The request times out after 30 seconds. + +For each attempt, the run log records a SHA-256 hash, a byte count and the field IDs, **never the values**. It also records the HTTP status line, **never the response body**. + +Only failures another attempt could fix are retried: a connection error, a timeout, a 5xx or a 429. A 4xx, a rejected acknowledgement (including HL7 `AE` and `AR`), an invalid payload, a missing Part 2 consent or an oversized response stop the run at once. When a run fails for good, department administrators get a generic notice with the workflow name, run ID and error code. + +## Changes, expiry and revocation + +| Event | Result | +|---|---| +| Any change to a step, template, condition, URL, header, credential, trigger or released field, by anyone who can edit workflows | **Pending approval** (*configuration changed*) until an administrator approves the current configuration | +| A delivery option changes (content type, success rule, saved values, idempotency header, If-None-Exist) | **Pending approval** (*configuration changed*) | +| A released custom field's sensitivity changes, or the field is disabled or removed | **Pending approval** (*configuration changed*) | +| The credential is deleted, its type changes, its OAuth2 token host changes, or it switches between client secret and private key JWT | **Suspended** (*credential changed*) | +| The credential's secret is rotated, or its private_key_jwt signing key is rotated | Stays **Active**; the rotation is written to the audit log | +| Protected Workflows turned off for the department | Every release **Suspended** | +| ADP offboarding scheduled, or ADP disabled | Every release **Revoked** | +| 12 months after approval | **Expired**. Administrators are emailed 30 and 7 days before | +| The workflow is deleted | The release is **Revoked** and its disclosure records are kept | + +A workflow whose release is anything other than **Active** is **skipped**. It never runs with `REDACTED` in place of the protected values, because that would overwrite the real values in the destination system. + +To renew, open the workflow and click **Renew**, then verify and attest again. The two-person rule applies to renewals too. While a renewal waits for its second approval, the release keeps sending until its current expiry date. If the configuration changed in the meantime, it goes back through a full approval instead. + +The workflow list shows each protected workflow's status: **Protected: Active**, **Pending approval**, **Suspended** (with the reason), **Expiring** (within 30 days), or **Expired**. + +## Protected workflows page and disclosure log + +Go to **Workflows → Protected workflows** to see every release with its fields, host, recipient, approver, expiry date and number of sends in the last 30 days. From this page you can **suspend** or **revoke** a release. **Renew** takes you to the workflow editor, where the attestation is shown. + +The **Disclosure log** has one record per send attempt and one per administrative action: enabled, disabled, requested, approved, suspended, revoked, expired and credential rotated. You can filter it by workflow, call ID and date, and **export it to CSV**. Records hold metadata only: + +- the fields released and the destination host +- the HTTP status +- the SHA-256 hash and byte length of the exact payload +- the broker request ID +- who acted and the outcome + +Records are **hash-chained per department**, and the page shows whether the chain verifies. Changing, removing or reordering any record breaks the verification. + +## Example: Microsoft Dataverse (Dynamics 365) + +1. In Microsoft Entra ID, register an application and add it as an application user in your Dataverse environment, with a security role that can update cases. +2. In Resgrid, add a workflow credential of type **OAuth2 Client Credentials**: + - Token URL: `https://login.microsoftonline.com//oauth2/v2.0/token` + - Client ID and client secret: from the app registration + - Scope: `https://.crm.dynamics.com/.default` +3. In Dataverse, give the case table an alternate key on a text column that holds the Resgrid call number, for example `new_resgridcallnumber`. The call number is not protected, so it can appear in a URL. `protected.*` values never can. +4. Create a **Call Closed** workflow that uses that credential. Dataverse updates a whole record with PATCH, which workflows do not offer, but it accepts a **PUT** for a single column, so add one **API PUT** step per column: + + | Step | URL | Output template | + |---|---|---| + | 1 | `https://.crm.dynamics.com/api/data/v9.2/incidents(new_resgridcallnumber='{{ call.number }}')/description` | `{ "value": "{{ protected.call.completed_notes \| json_escape }}" }` | + | 2 | `https://.crm.dynamics.com/api/data/v9.2/incidents(new_resgridcallnumber='{{ call.number }}')/new_resgridoutcome` | `{ "value": "{{ protected.call.udf.outcome \| json_escape }}" }` | + + To write the whole record in one request instead, send a single **API POST** to an Entra-protected Azure Function or Logic App that you own and that performs the PATCH. +5. In the **Protected release** panel, tick **Completion notes** and the **outcome** custom field. Enter the recipient, for example *County DMH, Dynamics 365 case management*, and the purpose, then attest and approve. + +The token endpoint host (`login.microsoftonline.com`) is pinned along with the Dataverse host. If the credential's token URL moves to another host, the release is suspended. + +## EHR integration + +Protected Workflows can send call outcomes into an electronic health record, either directly (FHIR REST, or a vendor REST or SOAP API) or through an interface engine such as Mirth or Rhapsody that accepts HTTPS and forwards HL7 v2 to the EHR. + +:::note No MLLP +Resgrid sends over HTTPS only. There is no HL7 v2 MLLP (raw TCP) action: to reach an EHR that only takes MLLP, send HL7 over HTTPS to an interface engine and let it forward the message. +::: + +**Subject identifiers.** Integrations put the EHR's IDs on a call as **subject identifiers**, a small set of keys and values kept apart from the call's external ID, for example `{ "ehr_client_id": "123456" }`. Send them as `SubjectIdentifiers` when creating or editing a call through the v4 API. Keys are lower-case letters, digits and underscores (up to 64 characters), values are up to 256 characters, with at most 20 keys. In an ADP department they are encrypted like any other protected call field. The call page shows them behind the usual reveal. + +**SMART Backend Services.** Most FHIR EHRs authenticate system clients with a signed JWT instead of a client secret. In the credential editor, choose **OAuth2 Client Credentials**, set **Client authentication** to **Private key JWT**, and pick RS384 (the default) or ES384. Resgrid generates the key pair when you save; the private key is encrypted and never shown. Register the credential's **JWKS URL** (`/api/v4/workflow-credentials//jwks.json`) with the EHR, or download the public JWK and upload it. **Rotate key** creates a new key; the old one stays in the JWKS for 7 days so the EHR can move over. + +**Templates.** **Workflows → New** has a template gallery. With Protected Workflows on, it offers two EHR samples: + +- **FHIR R4 Encounter and Observations**: a transaction Bundle with an Encounter (class `FLD`, `finished`, dispatch to close) for `Patient/{subject_ids.ehr_client_id}` and one Observation per released custom field. It uses conditional create on the call's identifier and saves the new Encounter ID as `ehr_encounter_id`. +- **HL7 v2.5.1 MDM^T02**: a "Crisis Field Response" document with the EHR client ID in `PID-3`, `run.idempotency_key` in `MSH-10` and one `OBX` per released custom field. It succeeds only on an `AA` acknowledgement. + +A template creates a disabled workflow with a placeholder destination, no credential and a draft protected release. Set the URL and credential, choose the fields, then request approval. + +Each release pins one destination, so use one workflow per destination. Several workflows can share a trigger: when a call closes, one workflow can write the case back to Dynamics 365 while another sends the encounter to the EHR, each with its own release, fields and disclosure records. + +## Technical reference + +| Item | Value | +|---|---| +| Routes | `/User/ProtectedWorkflows/{Index,Disclosures,DisclosuresCsv,Panel,StepUp}` and the JSON commands `SaveDepartmentSettings`, `SaveDraft`, `RequestApproval`, `Approve`, `Renew`, `Suspend`, `Revoke`, `DiscardDraft`, `SendTest` | +| API | `api/v4/ProtectedWorkflows/*`. Approve, renew, request and the department toggle refuse API keys and client-credentials tokens, and need a valid Protected Data Grant for the calling user in `X-Resgrid-Protected-Grant` | +| Permission | `ConfigureProtectedDataEgress` (38). A missing row resolves to Department Admins | +| Broker purpose | `protected-workflow` (workload decrypt lane; fresh request ID per attempt) | +| Tables | `WorkflowProtectedReleases`, `ProtectedWorkflowDisclosures` (append-only, hash-chained); toggle columns on `DepartmentProtectedDataEgressPolicies`; `Calls.SubjectIdentifiers` (ADP catalog 29, field `calls.subjectidentifiers`), `Calls.Part2ConsentOnFile`, `UdfFields.Sensitivity`, `WorkflowCredentials.PublicJwks` | +| Release field IDs | catalog IDs such as `calls.completednotes`; `calls.subjectidentifiers#` for one identifier; `calls.udf#` for one custom field | +| JWKS | `GET /api/v4/workflow-credentials/{credentialId}/jwks.json` (anonymous, public keys only) | +| Worker | ID 71, daily: expiry, ADP-offboarding revocation, toggle-off suspension, 30- and 7-day notices | +| Config | `DataProtectionConfig.ProtectedWorkflowReleaseLifetimeDays` (365), `ProtectedWorkflowHttpTimeoutSeconds` (30), `ProtectedWorkflowMaxFieldsPerRelease` (16), `ProtectedWorkflowStepUpFreshnessMinutes` (10), `ProtectedWorkflowAllowHttpBasicCredentials` (false), `ProtectedWorkflowExpiryNoticeDays` ("30,7"), `ProtectedWorkflowMaxResponseBytes` (1048576), `ProtectedWorkflowMaxCaptureKeys` (5), `WorkflowJwksOverlapDays` (7) | diff --git a/docs/web-app/workflows.md b/docs/web-app/workflows.md index d7a0170..37ee78c 100644 --- a/docs/web-app/workflows.md +++ b/docs/web-app/workflows.md @@ -44,6 +44,10 @@ Displays all workflows configured for the department with: Navigate to **Department → Workflows** and click **New Workflow**. +A trigger can have any number of workflows. When the event happens, every enabled workflow on that trigger gets its own run, with its own steps, retries and history. Your plan limits how many workflows a department can have in total. + +To start from a ready-made workflow, pick one under **Start from a template**: a JSON webhook for new calls or an email when a call closes. Departments using [Protected Workflows](protected-workflows) also see the FHIR R4 and HL7 v2 EHR samples. A template creates the workflow **disabled**, with a placeholder destination and no credential, so nothing runs until you set the URL and credential on each step and enable it. + ![New workflow](/img/web-app/workflows/new.png) ### Workflow Fields @@ -99,6 +103,10 @@ Hover over an array variable to see the child properties available inside each i See the [Workflows Configuration](../configuration/workflows) page for the full template variable reference. +:::tip Escaping values in structured payloads +Pass free text through `json_escape`, `xml_escape` or `hl7_escape` when you put it inside a JSON string, an XML document or an HL7 v2 field, for example `"notes": "{{ call.notes | json_escape }}"`. A quote or line break in the value then can't break the payload. `fhir_datetime` and `hl7_ts` format a date in UTC for FHIR and HL7. See [Workflow Variables](../reference/workflow-variables#template-helpers). +::: + ## Trigger Event Types Workflows can subscribe to any of the following system events: @@ -290,6 +298,9 @@ Navigate to **Department → Workflows → Credentials** to manage stored creden | **Azure Blob Storage** | Connection String (or Account Name + Account Key), Container Name | | **Box** | Developer Token or JWT credentials (Client ID, Client Secret, Enterprise ID, Private Key) | | **Dropbox** | App Key, App Secret, OAuth2 Refresh Token | +| **OAuth2 Client Credentials** | Token URL (https), Client ID, Scope, Audience (optional), and a **client authentication**: **Client secret**, or **Private key JWT** (SMART Backend Services, RS384 or ES384) where Resgrid generates and keeps the key pair and publishes the public key at `/api/v4/workflow-credentials/{id}/jwks.json`. The HTTP action fetches a bearer token and reuses it until 60 seconds before it expires. Use it for Microsoft Dataverse, Entra-protected Azure Functions or Logic Apps, and FHIR EHRs. | + +HTTP Bearer, HTTP Basic and HTTP API Key credentials are applied from their type, so the stored fields need no extra `authType`. ### Creating a Credential @@ -343,6 +354,12 @@ The **Pending** view lists all currently pending and in-progress workflow runs f - **Cancel** — Cancel an individual pending run - **Clear All** — Cancel all pending runs for the department (with confirmation dialog) +## Protected Workflows (Advanced Data Protection) + +In a department with [Advanced Data Protection](data-protection), workflow templates receive `REDACTED` in place of protected values. An administrator can approve a specific workflow as a [Protected Workflow](protected-workflows). It can then send selected fields, available as `protected.call.*`, to one pinned HTTPS destination through API POST or PUT steps. Any later edit to the workflow sends the approval back for re-approval. Every send is recorded in a disclosure log. + +Protected API steps also have delivery options for EHR integration: a checked content type (JSON, FHIR JSON, XML, SOAP, HL7 v2), a success rule (for example an HL7 `AA` acknowledgement), response values saved encrypted on the call, and an idempotency header. See [EHR integration](protected-workflows#ehr-integration). + ## Setup examples | Department type | How to set it up | @@ -368,6 +385,8 @@ When a workflow step fails: The `Max Retry Count` has a server-side ceiling of **5** to prevent infinite retry abuse. +A [Protected Workflow](protected-workflows) retries only failures another attempt could fix: a connection error, a timeout, a 5xx or a 429. Anything else (a 4xx, a rejected acknowledgement, an invalid payload) fails the run at once and notifies the department administrators. + ### Template Sandboxing Scriban templates are executed in a sandboxed environment to prevent abuse: