<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Shawn Bure</title>
    <description>The latest articles on DEV Community by Shawn Bure (@shawnbure).</description>
    <link>https://dev.to/shawnbure</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4051865%2Fa35ce99b-f9ed-4c19-8cfe-7b763ab5f431.jpg</url>
      <title>DEV Community: Shawn Bure</title>
      <link>https://dev.to/shawnbure</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/shawnbure"/>
    <language>en</language>
    <item>
      <title>Single-use invite links are capabilities, not permanent room URLs</title>
      <dc:creator>Shawn Bure</dc:creator>
      <pubDate>Wed, 05 Aug 2026 20:19:06 +0000</pubDate>
      <link>https://dev.to/shawnbure/single-use-invite-links-are-capabilities-not-permanent-room-urls-3k4</link>
      <guid>https://dev.to/shawnbure/single-use-invite-links-are-capabilities-not-permanent-room-urls-3k4</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;I created and maintain elm.chat, the open-source project used as the implementation example below. I developed this article with substantial AI assistance, then reviewed the code paths, tested the claims, and take responsibility for the final text.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A permanent room URL is easy to share and impossible to unshare. A safer invitation separates the authority to locate a room, enter it once, decrypt its content, reconnect, and administer it.&lt;/p&gt;

&lt;p&gt;This is the design behind elm.chat's current single-use invite flow. The reusable lesson applies to any capability-link system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not make one URL carry every kind of authority
&lt;/h2&gt;

&lt;p&gt;A reusable secret link often answers several questions at once: which room, who may enter, and which key decrypts the conversation. Forwarding that link forwards every capability it contains.&lt;/p&gt;

&lt;p&gt;elm.chat gives each concern a different value:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Room ID:&lt;/strong&gt; a path segment used to route the request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Invite token:&lt;/strong&gt; a query value the room validates for one admission.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Room secret:&lt;/strong&gt; a URL fragment used by browsers to derive the encryption key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Creator token:&lt;/strong&gt; a separate capability for issuing invites, revoking them, removing participants, and destroying the room.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/c/{roomId}?invite={inviteToken}#{roomSecret}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Browsers do not send the fragment in a normal HTTP request. The relay receives the room ID and invite token but not the room secret through ordinary navigation. This narrows the relay's authority; it does not make the connection anonymous.&lt;/p&gt;

&lt;h2&gt;
  
  
  Represent an invite as a state machine
&lt;/h2&gt;

&lt;p&gt;An invite is not a Boolean named &lt;code&gt;valid&lt;/code&gt;. It has time and ownership semantics.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Invite&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;createdAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;expiresAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;consumedAt&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;consumedBySessionId&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;revokedAt&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Creation requires the creator capability. The room generates the token, applies a ten-minute default lifetime with a one-minute floor, persists the record, and returns it only after storage succeeds.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consume before admitting
&lt;/h2&gt;

&lt;p&gt;The join transition rejects a missing, revoked, consumed, or expired invite. For the winning session, it writes &lt;code&gt;consumedAt&lt;/code&gt; and &lt;code&gt;consumedBySessionId&lt;/code&gt; before the first storage await.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;invite&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;invite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;revokedAt&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;invite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;consumedAt&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;invite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;expiresAt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nf"&gt;rejectJoin&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nx"&gt;invite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;consumedAt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;invite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;consumedBySessionId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sessionId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;invites&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;invite&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;invite&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;persistInvites&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;admitSocket&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That ordering matters. Another join handled by the same room sees the in-memory consumed state immediately, even while persistence is in progress.&lt;/p&gt;

&lt;p&gt;Keeping this transition inside one room-scoped Durable Object avoids a separate read-then-write race across stateless workers or database replicas. If your platform does not provide single-owner coordination, use a conditional write or transaction that changes exactly one unconsumed record.&lt;/p&gt;

&lt;p&gt;Cloudflare's &lt;a href="https://developers.cloudflare.com/durable-objects/best-practices/rules-of-durable-objects/" rel="noopener noreferrer"&gt;current Durable Objects rules&lt;/a&gt; describe each object as a globally unique, single-threaded coordination point and explain the input and output gates around storage.&lt;/p&gt;

&lt;p&gt;The practical guarantee is &lt;strong&gt;at most one new session admitted&lt;/strong&gt;, not magical exactly-once delivery. A failure after persistence can burn an invite without completing the join.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reconnect is not a second redemption
&lt;/h2&gt;

&lt;p&gt;At-most-once admission without reconnect semantics creates a brittle product: a page reload consumes a second invite or locks out the intended participant.&lt;/p&gt;

&lt;p&gt;elm.chat stores a random, room-scoped session ID in browser session storage. The session that consumed the invite may reconnect with the same ID while the invite remains unrevoked; a new session is rejected.&lt;/p&gt;

&lt;p&gt;This is continuity, not identity verification. Anyone who obtains the invite before redemption can still win the race, and browser state can be copied or compromised.&lt;/p&gt;

&lt;h2&gt;
  
  
  Revocation should end current authority too
&lt;/h2&gt;

&lt;p&gt;Revoking an unused invite prevents a future join. Revoking a consumed invite also disconnects its currently connected consuming session.&lt;/p&gt;

&lt;p&gt;Otherwise a creator-facing “revoke” button changes a database flag while leaving the admitted participant online.&lt;/p&gt;

&lt;p&gt;Room destruction is broader: it closes every socket and ends the room lifecycle. Neither action can erase plaintext a participant already saved, copied, photographed, forwarded, or backed up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the negative paths
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Redeem the same invite concurrently from two new sessions; only one may join.&lt;/li&gt;
&lt;li&gt;Reload the winning browser; the same session should reconnect without a new redemption.&lt;/li&gt;
&lt;li&gt;Open the consumed link in a different session; it must fail.&lt;/li&gt;
&lt;li&gt;Revoke an unused invite; later redemption must fail.&lt;/li&gt;
&lt;li&gt;Revoke a consumed invite; its connected session must be removed.&lt;/li&gt;
&lt;li&gt;Advance beyond expiry; redemption must fail even if the token was never used.&lt;/li&gt;
&lt;li&gt;Restart or hibernate the room owner; consumed and revoked state must survive.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you need funnel evidence, count creation and redemption only in aggregate. Do not put room IDs, invite tokens, participant IDs, or message data into growth analytics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Inspect the working implementation
&lt;/h2&gt;

&lt;p&gt;The complete TypeScript path is public:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/shawnbure/elm-chat/blob/main/apps/web/src/App.tsx" rel="noopener noreferrer"&gt;Browser invite and reconnect flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/shawnbure/elm-chat/blob/main/durable-objects/room/src/room.ts" rel="noopener noreferrer"&gt;Invite state machine and join transition&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/shawnbure/elm-chat/blob/main/docs/threat-model.md" rel="noopener noreferrer"&gt;Threat model and known limits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://elm.chat/single-use-invite-links?source=dev-community" rel="noopener noreferrer"&gt;Full article and self-host path&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;elm.chat has not had an independent security audit. Message authentication and replay/duplicate protection are unfinished, the Cloudflare relay sees ordinary connection metadata, and endpoints can retain copies. It is not an anonymity, high-risk, regulated-data, or production-finance system.&lt;/p&gt;

&lt;p&gt;The project is AGPL-3.0-or-later. If you want to test the coordination model, &lt;a href="https://elm.chat/deploy/cloudflare?source=dev-community" rel="noopener noreferrer"&gt;deploy it to your own Cloudflare account&lt;/a&gt; and inspect every claim.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>architecture</category>
      <category>security</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Fixing Deploy to Cloudflare in a monorepo: expose the root contract</title>
      <dc:creator>Shawn Bure</dc:creator>
      <pubDate>Wed, 05 Aug 2026 19:27:29 +0000</pubDate>
      <link>https://dev.to/shawnbure/fixing-deploy-to-cloudflare-in-a-monorepo-expose-the-root-contract-o4p</link>
      <guid>https://dev.to/shawnbure/fixing-deploy-to-cloudflare-in-a-monorepo-expose-the-root-contract-o4p</guid>
      <description>&lt;p&gt;Disclosure: I created elm.chat. AI tools substantially assisted drafting and implementation under my direction; I reviewed the code, tests, sources, and claims and remain responsible for the result. The project is early-stage and has not had an independent security audit.&lt;/p&gt;

&lt;p&gt;A deploy badge can look correct while every new user reaches &lt;strong&gt;No Wrangler&lt;br&gt;
configuration detected&lt;/strong&gt;. In a workspace repository, the fix is to make the&lt;br&gt;
target root a complete deployment contract.&lt;/p&gt;
&lt;h2&gt;
  
  
  The failure appears after the badge works
&lt;/h2&gt;

&lt;p&gt;elm.chat already had a working production deployment and a valid Deploy to&lt;br&gt;
Cloudflare link. Its Wrangler configuration lived under &lt;code&gt;workers/api&lt;/code&gt;, where&lt;br&gt;
local and production commands could find it. A new visitor followed the badge,&lt;br&gt;
however, and Cloudflare reported &lt;strong&gt;No Wrangler configuration detected&lt;/strong&gt; before&lt;br&gt;
falling back to automatic project configuration.&lt;/p&gt;

&lt;p&gt;That is the dangerous version of a deployment bug: maintainers can keep&lt;br&gt;
shipping while the public self-host path is broken. The badge tests only&lt;br&gt;
navigation. It does not prove that the target directory exposes everything the&lt;br&gt;
setup flow needs.&lt;/p&gt;
&lt;h2&gt;
  
  
  The target directory is the contract boundary
&lt;/h2&gt;

&lt;p&gt;Cloudflare analyzes the directory named by the deploy URL. If the URL targets&lt;br&gt;
the repository root, nested workspace configuration is not a substitute for a&lt;br&gt;
root deploy contract. The root needs a Wrangler configuration plus build and&lt;br&gt;
deploy scripts that work from that directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"build"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm run build --workspace @example/web &amp;amp;&amp;amp; npm run build --workspace @example/api"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"deploy"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npm run build &amp;amp;&amp;amp; wrangler deploy -c wrangler.jsonc"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The root scripts can delegate to workspaces. What matters is that a stranger—and&lt;br&gt;
Cloudflare's project analysis—can start at the target root without knowing the&lt;br&gt;
repository's internal layout.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keep the public template smaller than production when necessary
&lt;/h2&gt;

&lt;p&gt;elm.chat's production Worker uses an optional Analytics Engine binding for its&lt;br&gt;
own aggregate growth measurement. Cloudflare does not list Analytics Engine&lt;br&gt;
among the resources its deploy button automatically provisions, so the public&lt;br&gt;
root template omits that binding. Independent instances work without sending&lt;br&gt;
elm.chat measurement events.&lt;/p&gt;

&lt;p&gt;Do not copy every production binding into a one-click template by reflex.&lt;br&gt;
Include only resources the platform can provision or the deployer can supply&lt;br&gt;
during setup. Otherwise the button turns an optional integration into a&lt;br&gt;
first-run failure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two configurations require a drift test
&lt;/h2&gt;

&lt;p&gt;A root template and a workspace production configuration can quietly diverge.&lt;br&gt;
elm.chat runs a repository check that compares the Worker entry point, Durable&lt;br&gt;
Object binding, migration tag, assets directory, compatibility date, and&lt;br&gt;
resolved paths. The build fails if the shared runtime contract drifts.&lt;/p&gt;

&lt;p&gt;This keeps intentional differences explicit: production can retain an optional&lt;br&gt;
binding, while the independently deployable root template stays minimal and&lt;br&gt;
reproducible.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the public journey, not the maintainer journey
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Open the public repository as a visitor would.&lt;/li&gt;
&lt;li&gt;Follow the published deploy button rather than a private dashboard shortcut.&lt;/li&gt;
&lt;li&gt;Select an account and wait for repository analysis.&lt;/li&gt;
&lt;li&gt;Confirm the detected build command, deploy command, and root path.&lt;/li&gt;
&lt;li&gt;Stop and investigate if the fallback warning appears.&lt;/li&gt;
&lt;li&gt;Run both root and workspace Wrangler dry runs in continuous integration.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Cloudflare documents the supported button format, scripts, and provisioned&lt;br&gt;
resources in its &lt;a href="https://developers.cloudflare.com/workers/platform/deploy-buttons/" rel="noopener noreferrer"&gt;Deploy to Cloudflare&lt;br&gt;
guide&lt;/a&gt;.&lt;br&gt;
The elm.chat repair is public in &lt;a href="https://github.com/shawnbure/elm-chat/pull/89" rel="noopener noreferrer"&gt;pull request&lt;br&gt;
89&lt;/a&gt;, including its root&lt;br&gt;
configuration and parity check.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reusable lesson
&lt;/h2&gt;

&lt;p&gt;A one-click deploy feature is an external API. Its callers do not share the&lt;br&gt;
maintainer's working directory, cached settings, or mental model. Treat the&lt;br&gt;
target root as a stable interface, make every dependency visible there, and&lt;br&gt;
test it from the outside.&lt;/p&gt;




&lt;p&gt;elm.chat is an open-source disposable messenger and an inspectable case study,&lt;br&gt;
not an independently audited security product. Message authentication and&lt;br&gt;
replay/duplicate protection remain unfinished. The hosted Cloudflare relay can&lt;br&gt;
observe ordinary connection metadata, and participant devices can retain&lt;br&gt;
anything they receive. It is not positioned for anonymous, high-risk, or&lt;br&gt;
regulated communication.&lt;/p&gt;

&lt;p&gt;Originally published at &lt;a href="https://elm.chat/cloudflare-deploy-button-monorepo" rel="noopener noreferrer"&gt;https://elm.chat/cloudflare-deploy-button-monorepo&lt;/a&gt; on August 5, 2026.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>architecture</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Deletion is a distributed-systems contract, not a UI animation</title>
      <dc:creator>Shawn Bure</dc:creator>
      <pubDate>Tue, 04 Aug 2026 22:07:12 +0000</pubDate>
      <link>https://dev.to/shawnbure/deletion-is-a-distributed-systems-contract-not-a-ui-animation-3h85</link>
      <guid>https://dev.to/shawnbure/deletion-is-a-distributed-systems-contract-not-a-ui-animation-3h85</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI-assistance disclosure:&lt;/strong&gt; I used AI for source discovery, structure, and editing. I manually reviewed the technical claims against the public code and primary platform documentation, and I stand behind the analysis.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A disappearing row proves only that one interface stopped rendering it. Engineers who promise deletion have to define what happens across clients, relays, databases, caches, logs, queues, replicas, backups, and recovery paths.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deletion is an end-to-end property
&lt;/h2&gt;

&lt;p&gt;In a distributed system, data rarely has one address. A user action can produce an application record, search index entry, cache value, analytics event, retry payload, queue message, log line, database replica, backup block, notification preview, and one or more client-side copies. Hiding the application record does not resolve the rest.&lt;/p&gt;

&lt;p&gt;A credible deletion promise therefore needs a scope, an authority, a deadline, and a failure model. Scope identifies the copies the service controls. Authority identifies who may trigger destruction. The deadline says when controlled copies become inaccessible and when they are physically reclaimed. The failure model explains what happens when a component is offline, a retry arrives late, or recovery restores older state.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with a state inventory, not a delete endpoint
&lt;/h2&gt;

&lt;p&gt;Before designing an API, list every place the data can exist and why it exists there. Classify each location as authoritative storage, derived storage, transit, operational telemetry, backup, or participant-controlled state. Give every class an owner and a retention rule. If a team cannot enumerate a copy, it cannot make a defensible claim about deleting that copy.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State class&lt;/th&gt;
&lt;th&gt;Typical examples&lt;/th&gt;
&lt;th&gt;Deletion question&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Authoritative&lt;/td&gt;
&lt;td&gt;Primary database, object storage&lt;/td&gt;
&lt;td&gt;What event makes the record unreachable?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Derived&lt;/td&gt;
&lt;td&gt;Indexes, caches, thumbnails&lt;/td&gt;
&lt;td&gt;How are stale derivatives invalidated?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transit&lt;/td&gt;
&lt;td&gt;Queues, retries, relay buffers&lt;/td&gt;
&lt;td&gt;Can an old payload recreate deleted state?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Operational&lt;/td&gt;
&lt;td&gt;Logs, traces, analytics&lt;/td&gt;
&lt;td&gt;Was sensitive content excluded before collection?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Recovery&lt;/td&gt;
&lt;td&gt;Replicas, snapshots, backups&lt;/td&gt;
&lt;td&gt;How is destruction preserved after restore?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Participant&lt;/td&gt;
&lt;td&gt;Downloads, screenshots, exports&lt;/td&gt;
&lt;td&gt;Which copies are outside service control?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Felm.chat%2Fdeletion-state-inventory.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Felm.chat%2Fdeletion-state-inventory.svg" alt="Six distributed data state classes: authoritative, derived, transit, operational, recovery, and participant-controlled copies" width="1200" height="675"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Inventory every controlled copy before defining a deletion promise.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Model destruction as an irreversible state transition
&lt;/h2&gt;

&lt;p&gt;A boolean such as deleted=true is usually too weak. Systems often need a lifecycle that distinguishes an active object from one that is closed to new writes, destroyed for normal access, and eventually reclaimed from recovery media. The transition into the destroyed state should be monotonic: delayed requests, retries, and replayed events must not reopen it.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ACTIVE -&amp;gt; SEALED -&amp;gt; DESTROYED -&amp;gt; RECLAIMED
      |          |
      +--expire--+

Invariant: no transition leaves DESTROYED or RECLAIMED.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Store the destruction marker wherever the system stores the object's identity, and make every write path check it. If a backup restore can resurrect an older active record, the recovery procedure must replay later destruction markers before the object becomes reachable.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Felm.chat%2Fdeletion-state-lifecycle.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Felm.chat%2Fdeletion-state-lifecycle.svg" alt="Irreversible lifecycle from Active to Sealed, Destroyed, and Reclaimed, with expiry leading to destruction" width="1200" height="675"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Retries and restores may move forward, never out of destruction.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate the control plane from the data plane
&lt;/h2&gt;

&lt;p&gt;The control plane answers whether an object exists, who may use it, when it expires, and whether destruction has occurred. The data plane carries the content. Keeping these responsibilities separate makes it possible to retain the minimum state needed to enforce an irreversible tombstone without retaining the content that the tombstone is meant to retire.&lt;/p&gt;

&lt;p&gt;This separation also sharpens review. A reviewer can ask whether the control plane authorizes destruction correctly, whether the data plane writes content anywhere durable, and whether either plane leaks content into logs or metrics. Encryption helps with content confidentiality, but it does not answer lifecycle, metadata, or endpoint questions by itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Design for the failures that make data return
&lt;/h2&gt;

&lt;p&gt;Most deletion bugs are resurrection bugs. A mobile client reconnects with an old offline mutation. A queue retries a create event after a tombstone. A cache repopulates from a lagging replica. A restore process brings back a record but not the later delete. A search index remains queryable after the primary row is gone. These are ordinary distributed-systems behaviors, so deletion tests must exercise them deliberately.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make write operations conditional on the current lifecycle state.&lt;/li&gt;
&lt;li&gt;Give destructive transitions stable, idempotent identifiers.&lt;/li&gt;
&lt;li&gt;Expire queued and offline mutations before they can outlive their purpose.&lt;/li&gt;
&lt;li&gt;Propagate invalidation to indexes and caches through observable workflows.&lt;/li&gt;
&lt;li&gt;Keep content out of logs, traces, error reports, and analytics by construction.&lt;/li&gt;
&lt;li&gt;Document backup retention separately from interactive deletion latency.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Verification needs negative evidence
&lt;/h2&gt;

&lt;p&gt;A successful API response proves that one component accepted a request. It does not prove that the system can no longer serve, search, replay, restore, or infer the data. Verification should test absence across every controlled state class and should repeat those checks after component restarts, delayed delivery, cache refresh, replica catchup, and recovery exercises.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create uniquely identifiable test content without using real sensitive data.&lt;/li&gt;
&lt;li&gt;Confirm each intended state class receives—or deliberately never receives—it.&lt;/li&gt;
&lt;li&gt;Trigger expiry and explicit destruction through every authorized path.&lt;/li&gt;
&lt;li&gt;Attempt reads, writes, searches, reconnects, retries, exports, and sync operations.&lt;/li&gt;
&lt;li&gt;Restart components and replay delayed messages that predate destruction.&lt;/li&gt;
&lt;li&gt;Restore a pre-destruction snapshot, then apply the documented recovery procedure.&lt;/li&gt;
&lt;li&gt;Check logs, traces, metrics, crash reports, indexes, and caches for the marker.&lt;/li&gt;
&lt;li&gt;Record which participant-controlled copies remain outside the service boundary.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Felm.chat%2Fdeletion-verification-loop.svg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Felm.chat%2Fdeletion-verification-loop.svg" alt="Deletion verification loop: create a harmless marker, destroy it, attack recovery paths, search controlled state, and record the remaining boundary" width="1200" height="675"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Verification seeks negative evidence across restart and recovery paths.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  User language should match the system boundary
&lt;/h2&gt;

&lt;p&gt;"Gone forever" is almost never an engineering statement. A service can promise that it no longer serves content, that controlled content stores were purged, or that backups age out within a documented window. It cannot promise that a recipient forgot, that a screenshot vanished, that a compromised endpoint was cleaned, or that network metadata was never observed unless the architecture actually provides those properties.&lt;/p&gt;

&lt;p&gt;Product copy should name the actor and the scope: "the relay no longer retains the room" is testable; "this conversation leaves no trace" is not. Honest language is not a conversion penalty. It is part of the interface contract, especially when users are choosing a tool because they want less durable data.&lt;/p&gt;

&lt;h2&gt;
  
  
  elm.chat as an inspectable, imperfect case study
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://elm.chat/?source=dev-community" rel="noopener noreferrer"&gt;elm.chat&lt;/a&gt; applies a narrow version of this model. A Durable Object stores room policy, status, creator capability, and invite state. Connected browsers hold the current conversation history. The relay forwards encrypted messages and file chunks without persisting a server-side transcript. Destroying the room changes its control-plane state so later joins and writes are rejected.&lt;/p&gt;

&lt;p&gt;The tradeoffs are explicit. Cloudflare can observe IP addresses, connection timing, sizes, and presence. Participants can retain plaintext. Peer-supplied history is not a trustworthy archive, and message authentication plus replay and duplicate protection remain unfinished. The project has not had an independent security audit and is not an anonymity, compliance, whistleblowing, or high-risk communications system.&lt;/p&gt;

&lt;h2&gt;
  
  
  The design review question
&lt;/h2&gt;

&lt;p&gt;Do not ask only, "Where is the delete button?" Ask, "Which controlled copies can still influence behavior after destruction, and what evidence proves they cannot bring the object back?" That question turns deletion from a user-interface gesture into a system property engineers can model, test, monitor, and explain.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/shawnbure/elm-chat" rel="noopener noreferrer"&gt;complete source, architecture, threat model, and deployment path&lt;/a&gt; are public under AGPL-3.0 for anyone who wants to challenge the example or adapt the design to a different infrastructure.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://elm.chat/deletion-distributed-systems-contract" rel="noopener noreferrer"&gt;elm.chat&lt;/a&gt;. Cross-posted here with the original URL set as canonical.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>distributedsystems</category>
      <category>architecture</category>
      <category>devops</category>
      <category>testing</category>
    </item>
    <item>
      <title>Building ephemeral encrypted chat with Cloudflare Durable Objects</title>
      <dc:creator>Shawn Bure</dc:creator>
      <pubDate>Mon, 03 Aug 2026 20:22:24 +0000</pubDate>
      <link>https://dev.to/shawnbure/building-ephemeral-encrypted-chat-with-cloudflare-durable-objects-aaf</link>
      <guid>https://dev.to/shawnbure/building-ephemeral-encrypted-chat-with-cloudflare-durable-objects-aaf</guid>
      <description>&lt;p&gt;Disclosure: I created elm.chat and used AI tools for drafting and implementation support under my direction. I reviewed the architecture, code, tests, and claims and remain responsible for the result.&lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://elm.chat/?source=dev-community" rel="noopener noreferrer"&gt;elm.chat&lt;/a&gt; around a narrow idea: a conversation should not automatically become a permanent server-side asset.&lt;/p&gt;

&lt;p&gt;It is not a replacement for a contact network or a claim that metadata can disappear. It is a disposable room for one live conversation. The interesting engineering question was how to make that room reliable without giving the server a readable or persistent transcript.&lt;/p&gt;

&lt;p&gt;The current answer is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;React and Web Crypto in the browser&lt;/li&gt;
&lt;li&gt;one Cloudflare Worker for the app and API&lt;/li&gt;
&lt;li&gt;one Durable Object per room&lt;/li&gt;
&lt;li&gt;one WebSocket per participant&lt;/li&gt;
&lt;li&gt;encrypted content relayed, but not persisted, by the Durable Object&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The complete project is &lt;a href="https://github.com/shawnbure/elm-chat" rel="noopener noreferrer"&gt;AGPL-3.0 source&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  One coordination boundary per room
&lt;/h2&gt;

&lt;p&gt;A room is a natural Durable Object boundary. One object owns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;room policy and lifecycle&lt;/li&gt;
&lt;li&gt;participant presence&lt;/li&gt;
&lt;li&gt;creator capabilities&lt;/li&gt;
&lt;li&gt;single-use invite state&lt;/li&gt;
&lt;li&gt;WebSocket relay&lt;/li&gt;
&lt;li&gt;expiry and destruction&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The Worker creates room metadata, addresses the object by room ID, and routes subsequent API and WebSocket traffic to it.&lt;/p&gt;

&lt;p&gt;The object stores the room policy, status, creator token, and invite records. It does &lt;strong&gt;not&lt;/strong&gt; store message envelopes, encrypted file chunks, or a transcript.&lt;/p&gt;

&lt;p&gt;That distinction matters. “Encrypted at rest” would still make the server a durable archive. elm.chat instead asks currently connected clients to hold the conversation in memory.&lt;/p&gt;

&lt;h2&gt;
  
  
  The room secret stays in the fragment
&lt;/h2&gt;

&lt;p&gt;Room creation starts in the browser:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate a random 256-bit room secret.&lt;/li&gt;
&lt;li&gt;Create room metadata through the Worker.&lt;/li&gt;
&lt;li&gt;Navigate to &lt;code&gt;/c/:roomId#&amp;lt;room_secret&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Derive the room key locally with HKDF-SHA-256.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The fragment is not included in normal HTTP requests, so the Worker receives the room ID but not the room secret. A strict &lt;code&gt;Referrer-Policy: no-referrer&lt;/code&gt; reduces accidental capability leakage to other origins.&lt;/p&gt;

&lt;p&gt;The browser uses the derived AES-GCM-256 key for message and file content:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;each message gets a fresh random 96-bit nonce&lt;/li&gt;
&lt;li&gt;files are divided into 64 KiB chunks&lt;/li&gt;
&lt;li&gt;each file chunk is encrypted independently&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The relay receives ciphertext envelopes and routing information, not plaintext.&lt;/p&gt;

&lt;h2&gt;
  
  
  One WebSocket carries the live room
&lt;/h2&gt;

&lt;p&gt;After joining, a client uses one WebSocket for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;presence&lt;/li&gt;
&lt;li&gt;encrypted messages&lt;/li&gt;
&lt;li&gt;encrypted file chunks&lt;/li&gt;
&lt;li&gt;transcript-sync requests and responses&lt;/li&gt;
&lt;li&gt;participant removal&lt;/li&gt;
&lt;li&gt;destruction events&lt;/li&gt;
&lt;li&gt;keepalive traffic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;WebSocket attachments are the source of truth for live membership. They survive Durable Object hibernation, so the object can wake and continue targeted routing without relying on a process-local participant map.&lt;/p&gt;

&lt;p&gt;When a new participant joins, the server does not load a transcript. The client requests one from peers that are already connected. A peer sends its current encrypted history through the relay, with a hard cap on the number of synced messages.&lt;/p&gt;

&lt;p&gt;If nobody connected still has an item, it is gone. That is intentional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I did not use direct WebRTC
&lt;/h2&gt;

&lt;p&gt;Peer-to-peer sounds like the obvious privacy choice, but the transport tradeoff is more complicated.&lt;/p&gt;

&lt;p&gt;Direct WebRTC ICE negotiation can reveal participant IP addresses to other people in the room. Connectivity can also fail on mobile carriers, symmetric NAT, and restrictive networks unless the application adds a TURN relay.&lt;/p&gt;

&lt;p&gt;I chose a Durable Object relay because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;room members never connect directly&lt;/li&gt;
&lt;li&gt;participants do not learn one another’s IP addresses&lt;/li&gt;
&lt;li&gt;the same transport works across restrictive networks&lt;/li&gt;
&lt;li&gt;there is no separate STUN/TURN service to operate&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This does &lt;strong&gt;not&lt;/strong&gt; eliminate metadata.&lt;/p&gt;

&lt;p&gt;Cloudflare still sees each connection’s IP address, timing, sizes, and presence. An observer can infer activity bursts even without decrypting content. The architecture moves content trust away from the server while accepting that the relay remains a metadata observer.&lt;/p&gt;

&lt;p&gt;That is the actual claim—not “no trace.”&lt;/p&gt;

&lt;h2&gt;
  
  
  One-time membership
&lt;/h2&gt;

&lt;p&gt;The room creator holds a creator capability in local browser storage and can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;issue single-use invites&lt;/li&gt;
&lt;li&gt;revoke invites&lt;/li&gt;
&lt;li&gt;remove participants&lt;/li&gt;
&lt;li&gt;destroy the room&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A non-creator needs a valid invite to join. The invite is consumed by the first new session that uses it. That same session can reconnect after a reload, but another session cannot reuse the link.&lt;/p&gt;

&lt;p&gt;This makes an elm.chat URL a handoff for one participant, not a permanent public room address.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the current design does not solve
&lt;/h2&gt;

&lt;p&gt;End-to-end encryption and ephemerality do not make a system invulnerable.&lt;/p&gt;

&lt;p&gt;The current build does not solve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;screenshots or copied plaintext&lt;/li&gt;
&lt;li&gt;compromised devices or browser extensions&lt;/li&gt;
&lt;li&gt;malicious recipients&lt;/li&gt;
&lt;li&gt;traffic analysis&lt;/li&gt;
&lt;li&gt;denial of service&lt;/li&gt;
&lt;li&gt;strong anonymous routing&lt;/li&gt;
&lt;li&gt;verified human identity&lt;/li&gt;
&lt;li&gt;forward secrecy beyond the shared room key&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The clients generate ephemeral identity keys, but those keys are not yet used to authenticate messages. Replay and duplicate protection are also unfinished. The project has also &lt;strong&gt;not had an independent security audit&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Those are not footnotes. They define where the current system should and should not be trusted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run it or review it
&lt;/h2&gt;

&lt;p&gt;The repository includes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the &lt;a href="https://github.com/shawnbure/elm-chat/blob/main/docs/architecture.md" rel="noopener noreferrer"&gt;architecture&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;the &lt;a href="https://github.com/shawnbure/elm-chat/blob/main/docs/threat-model.md" rel="noopener noreferrer"&gt;threat model&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;a one-click Cloudflare deployment path&lt;/li&gt;
&lt;li&gt;manual Wrangler deployment instructions&lt;/li&gt;
&lt;li&gt;contributor guidance and scoped issues&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can &lt;a href="https://elm.chat/?source=dev-community" rel="noopener noreferrer"&gt;try the live room&lt;/a&gt;, &lt;a href="https://github.com/shawnbure/elm-chat" rel="noopener noreferrer"&gt;inspect the source&lt;/a&gt;, &lt;a href="https://elm.chat/deploy/cloudflare?source=dev-community" rel="noopener noreferrer"&gt;deploy it to your own Cloudflare account&lt;/a&gt;, or review the current &lt;a href="https://elm.chat/security-and-limitations" rel="noopener noreferrer"&gt;security status and limitations&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The feedback I am most interested in is on:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;the encrypted-relay versus WebRTC tradeoff&lt;/li&gt;
&lt;li&gt;one Durable Object per room as the coordination boundary&lt;/li&gt;
&lt;li&gt;client-supplied transcript sync after object hibernation&lt;/li&gt;
&lt;li&gt;the message-authentication path that should come next&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you find a security issue, please use the repository’s private reporting instructions rather than publishing an exploitable detail.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>typescript</category>
      <category>privacy</category>
      <category>architecture</category>
    </item>
    <item>
      <title>AI Does Not Need Another Demo. It Needs an Operating Boundary.</title>
      <dc:creator>Shawn Bure</dc:creator>
      <pubDate>Tue, 28 Jul 2026 17:57:47 +0000</pubDate>
      <link>https://dev.to/shawnbure/ai-does-not-need-another-demo-it-needs-an-operating-boundary-2n7p</link>
      <guid>https://dev.to/shawnbure/ai-does-not-need-another-demo-it-needs-an-operating-boundary-2n7p</guid>
      <description>&lt;p&gt;The model is important. The system around the model determines whether a company can trust it with real work.&lt;/p&gt;

&lt;p&gt;Most AI projects begin with a model demonstration. Someone uploads a document, asks a difficult question, and gets an answer quickly enough to change the room.&lt;/p&gt;

&lt;p&gt;That moment is useful. It proves capability. It does not prove that the company has a production system.&lt;/p&gt;

&lt;p&gt;A production system has to answer a different set of questions. What information can the model receive? Which tools can it use? What happens when it is uncertain? Who approves a consequential action? What is logged? What can be retained? What happens when a provider is unavailable?&lt;/p&gt;

&lt;h2&gt;
  
  
  The model is only one participant
&lt;/h2&gt;

&lt;p&gt;At &lt;a href="https://workrr.ai/" rel="noopener noreferrer"&gt;Workrr.ai&lt;/a&gt;, we primarily build with OpenAI. The reasoning, multimodal, tool-use, and engineering capabilities make it possible to address work that traditional automation could not handle well.&lt;/p&gt;

&lt;p&gt;But a model should not quietly become the application, the security policy, the database, and the audit trail. Those are separate responsibilities.&lt;/p&gt;

&lt;p&gt;We use Cloudflare as the production control plane around the AI capability. Depending on the project, that can include application compute, identity and access controls, AI Gateway, state, storage, queues, retrieval, rate limits, observability, and provider routing.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;OpenAI supplies the capability. Cloudflare helps define and enforce the operating boundary. The business still owns the decision about what the system is allowed to do.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Privacy is not a yes-or-no feature
&lt;/h2&gt;

&lt;p&gt;"Is this private?" sounds like a simple question. In practice, privacy is a chain of architectural decisions.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What data enters the workflow?&lt;/li&gt;
&lt;li&gt;Where is it stored, and for how long?&lt;/li&gt;
&lt;li&gt;Which provider is permitted to process it?&lt;/li&gt;
&lt;li&gt;Can high-risk information be removed or transformed first?&lt;/li&gt;
&lt;li&gt;Which actions require a person?&lt;/li&gt;
&lt;li&gt;Can part of the work use private inference while deeper reasoning uses OpenAI only when approved?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last question is why hybrid AI matters. A company does not have to choose one model and force every task through it. The system can route work according to capability, risk, latency, cost, and policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with one workflow that already hurts
&lt;/h2&gt;

&lt;p&gt;The strongest starting point is rarely "we need an AI strategy." It is usually a workflow that repeats, crosses several systems, consumes meaningful labor, and still produces defects or delays.&lt;/p&gt;

&lt;p&gt;We saw that while building Reclaira.ai for Paramount Billing Solutions, where collections and accounts-receivable work depends on context, prioritization, consistent communication, and human judgment. We saw it again with GlassMaster for 2U Glass &amp;amp; Tint, where operational knowledge, communications, business data, and execution had to become one usable system.&lt;/p&gt;

&lt;p&gt;The industries were different. The architectural lesson was the same: useful AI has to operate inside the company's real constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a credible first deployment should prove
&lt;/h2&gt;

&lt;p&gt;A first production deployment does not need to transform the entire company. It should prove a few things clearly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The system reduces measurable friction.&lt;/li&gt;
&lt;li&gt;Its outputs can be evaluated against real examples.&lt;/li&gt;
&lt;li&gt;Its permissions and data path are understandable.&lt;/li&gt;
&lt;li&gt;A person remains accountable where consequences require it.&lt;/li&gt;
&lt;li&gt;The system creates evidence that improves the next version.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once those foundations exist, expansion becomes an operating decision rather than a leap of faith.&lt;/p&gt;

&lt;h2&gt;
  
  
  The opportunity is larger than chat
&lt;/h2&gt;

&lt;p&gt;Chat interfaces are useful, but the more important opportunity is operational memory: a system that can understand requests, retrieve context, use tools, coordinate actions, preserve evidence, and help the company learn from the history of its own work.&lt;/p&gt;

&lt;p&gt;That is what we are building toward at Workrr.ai as an OpenAI Partner: OpenAI-led systems with a Cloudflare-native control plane and private or hybrid paths when the work requires a tighter boundary.&lt;/p&gt;




&lt;p&gt;Originally published at &lt;a href="https://workrr.ai/articles/ai-needs-an-operating-boundary/" rel="noopener noreferrer"&gt;workrr.ai&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>openai</category>
      <category>privacy</category>
      <category>devops</category>
    </item>
  </channel>
</rss>
