Back to Feed
article 4m read

Move Workflows Behind One Coherent Capability Surface

Workflows become easier to trust when reads, previews, writes, idempotency, audit, permissions, and deployment boundaries share one explicit capability contract.

Article DepthPractice

Flow

Build and operate the workflow

  1. 1Inventory

    List reads, plans, writes, permissions, targets, and receipts.

  2. 2Guard

    Validate inputs, confirmation, idempotency, and adapter boundaries.

  3. 3Persist

    Create durable mutation records before external side effects.

  4. 4Verify

    Test concurrency, failure recovery, deployment ownership, and read refresh.

Start with a capability inventory. For each resource and action, write down its name, read or write nature, target identifier, accepted inputs, required permission, confirmation rule, external side effects, durable record, and expected result. Include actions that seem harmless, such as fetching raw details or refreshing a task feed. These often become data-leak or consistency boundaries when they are exposed through a new client.

Separate the plan from the execution method. A plan should be a pure value: operation name, target, normalized input, and an explanation of the intended change. It should not call the provider, write a cache, or create an audit record that looks like a completed action. The execution path should accept only the plan's validated input plus an explicit confirmation and idempotency key. If an action cannot be described without reaching out to the provider, identify that fact instead of calling it a dry run.

Design the mutation envelope before writing UI code. A useful envelope contains an idempotency key, dry-run state, confirmation state, and typed input. Validate timestamps, identifiers, status values, labels, and list membership at the boundary. Keep deletion or other irreversible actions behind a stronger confirmation than an ordinary update. Avoid accepting arbitrary provider payloads from the browser; map a small domain input into the provider's request format on the server.

Implement the ledger as a first-class component. Use a stable digest-derived filename or database key so a request cannot escape its record namespace. Create the pending record atomically before the external call. Store the action, target, request payload, audit ID, start time, status, completion time, result, and error. On a repeated key, return a succeeded result as replayed. For pending or failed records, stop and expose the audit ID so an operator can inspect the outcome. Do not automatically retry an external mutation after a timeout.

Make reads refreshable without making them authoritative for writes. After a successful mutation, invalidate or refresh the relevant feed and detail view. If the refresh fails, show the write receipt and the refresh problem separately. This distinction is essential: the provider may have accepted the change even though the next read was unavailable.

Put a narrow adapter in front of the backend. Allowlist exact resource patterns and HTTP methods. Preserve repeated query parameters, reject unknown paths, cap query strings and request bodies, require JSON where appropriate, refuse redirects, verify the upstream content type, and enforce a response-size limit. Do not forward browser cookies, bearer tokens, or arbitrary destination URLs. Avoid retries for writes; for a read, use a retry policy only when the operation is demonstrably safe.

Treat the deployment as part of the feature. Bind the backend to a loopback or private address, route only known paths through the host application, use an administrator or least-privilege boundary, and keep state in a dedicated directory with restrictive permissions. Set memory and CPU limits when the new service is optional. Preserve the existing service as a documented rollback path until the new surface has passed acceptance checks.

Test behavior, not only functions. Exercise ten concurrent submissions with one idempotency key and prove that they produce one external operation and one durable record. Test a changed head or version before submission, unsupported flags, unauthorized scopes, oversized requests, redirects, non-JSON responses, network timeouts, and stale UI refreshes. Include a test that an unmanaged installed file is never overwritten by an installer.

Measure the workflow after release. Track the ratio of previews to writes, confirmation rejections, replayed requests, pending records, unknown outcomes, provider failures, refresh failures, and time to reconcile a successful write into the read model. Review those measurements with the audit trail. A capability surface is healthy when the system makes the normal path easy and the uncertain path explicit.

Keep reading

Pick up a connected idea or branch into a nearby one.

5 paths forward