<?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: Boitumelo Phetla</title>
    <description>The latest articles on DEV Community by Boitumelo Phetla (@boitumelo_phetla_022b64a2).</description>
    <link>https://dev.to/boitumelo_phetla_022b64a2</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%2F4195727%2Fff55a700-30b0-49bc-bc92-c072414a05c4.png</url>
      <title>DEV Community: Boitumelo Phetla</title>
      <link>https://dev.to/boitumelo_phetla_022b64a2</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/boitumelo_phetla_022b64a2"/>
    <language>en</language>
    <item>
      <title>Introducing Andika Library</title>
      <dc:creator>Boitumelo Phetla</dc:creator>
      <pubDate>Thu, 22 Oct 2026 09:51:48 +0000</pubDate>
      <link>https://dev.to/boitumelo_phetla_022b64a2/introducing-andika-library-1k52</link>
      <guid>https://dev.to/boitumelo_phetla_022b64a2/introducing-andika-library-1k52</guid>
      <description>&lt;p&gt;We are building a house consciously, with intention, purpose and a philosophy behind every brick. Today we released Andika Library.&lt;/p&gt;

&lt;p&gt;Library is a home for scattered documents, research, files and an organisation's collective knowledge. It is meant for an individual making sense of years of information, and for an enterprise managing thousands of assets.&lt;/p&gt;

&lt;p&gt;Governance is part of the architecture, not an afterthought. Library comes with mutually exclusive permissions, intended to set clear boundaries around ownership, access and control.&lt;/p&gt;

&lt;p&gt;Every asset in your Library can be brought into your Workspaces and Workbooks, so documents, research and information become part of the work you write, without compromising the permissions that protect them.&lt;/p&gt;

&lt;p&gt;The Library is presented like a library of books.&lt;/p&gt;

&lt;p&gt;One Library. Connected knowledge. Governance by design.&lt;/p&gt;

&lt;p&gt;Originally published on the Andika journal: &lt;a href="https://andika.work/blog/dd4d6274-2cd9-44ed-ac6f-45e1167536fd" rel="noopener noreferrer"&gt;https://andika.work/blog/dd4d6274-2cd9-44ed-ac6f-45e1167536fd&lt;/a&gt;. Adapted for DEV with AI assistance.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>documentation</category>
      <category>security</category>
    </item>
    <item>
      <title>Tasks where the work lives: why colocation beats features</title>
      <dc:creator>Boitumelo Phetla</dc:creator>
      <pubDate>Tue, 20 Oct 2026 15:20:09 +0000</pubDate>
      <link>https://dev.to/boitumelo_phetla_022b64a2/tasks-where-the-work-lives-why-colocation-beats-features-2383</link>
      <guid>https://dev.to/boitumelo_phetla_022b64a2/tasks-where-the-work-lives-why-colocation-beats-features-2383</guid>
      <description>&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%2Fandika.work%2Fapi%2Fpublic%2Fassets%2F32f59508-4eae-4e36-bf2c-4d5dab3ff54b%2Fcontent" 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%2Fandika.work%2Fapi%2Fpublic%2Fassets%2F32f59508-4eae-4e36-bf2c-4d5dab3ff54b%2Fcontent" alt="Creating a task" width="1402" height="1122"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Most teams keep their knowledge in one tool and their tasks in another. The document says what needs doing; the task tracker says who is doing it; and the link between the two is a paste of a URL that nobody clicks.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea: put tasks next to the work
&lt;/h2&gt;

&lt;p&gt;Kanban-style task management is well understood: cards, columns, a visible flow from todo to done. What changes when the board lives inside the same workspace as the documents, research, and notes is not the mechanics but the distance. The task that says "update the methods section" can sit one click from the methods section. Assignment, status, and context stop being three different apps.&lt;/p&gt;

&lt;p&gt;Colocation also lowers the bar for &lt;em&gt;personal&lt;/em&gt; tasks. Assigning something to yourself in a heavyweight project tracker feels absurd; assigning it where your notes already live feels like leaving yourself a bookmark with a deadline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trade-offs
&lt;/h2&gt;

&lt;p&gt;A built-in board will never match a dedicated tracker feature for feature: no Gantt charts, no sprint analytics, no automation rules. For large teams running formal processes, a real project-management tool wins. The integrated board wins on a different axis: adoption. The tool people actually open every day beats the tool with more features that nobody checks.&lt;/p&gt;

&lt;h2&gt;
  
  
  How we approached it at Andika
&lt;/h2&gt;

&lt;p&gt;Andika is a knowledge workspace, and the task feature arrived the way most of our features do: someone asked. A colleague mentioned how useful it would be to assign tasks to yourself and your teammates, and we built it in about four hours. Our engineers immediately started creating and assigning tasks among themselves, which gave us a clearer way to coordinate work without leaving the workspace.&lt;/p&gt;

&lt;p&gt;It follows a simple Kanban framework. Earlier feedback in the same spirit led to private document views alongside shareable, view-only links, so you control who sees and who edits.&lt;/p&gt;

&lt;p&gt;The lesson we keep relearning: the fastest way to build the right thing is to stay close enough to users that a conversation on Monday can be a feature by Monday afternoon.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Adapted from a post on the &lt;a href="https://andika.work/blog/530361a6-92e0-4de4-bdde-c26eb8c75a45" rel="noopener noreferrer"&gt;Andika blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>kanban</category>
      <category>collaboration</category>
      <category>tools</category>
    </item>
    <item>
      <title>A bibliography is a list: mapping the constellation around a paper</title>
      <dc:creator>Boitumelo Phetla</dc:creator>
      <pubDate>Tue, 20 Oct 2026 14:13:12 +0000</pubDate>
      <link>https://dev.to/boitumelo_phetla_022b64a2/a-bibliography-is-a-list-mapping-the-constellation-around-a-paper-2168</link>
      <guid>https://dev.to/boitumelo_phetla_022b64a2/a-bibliography-is-a-list-mapping-the-constellation-around-a-paper-2168</guid>
      <description>&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%2Fandika.work%2Fapi%2Fpublic%2Fassets%2F66b8cb5c-bc73-42f7-8821-ae8515b545ef%2Fcontent" 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%2Fandika.work%2Fapi%2Fpublic%2Fassets%2F66b8cb5c-bc73-42f7-8821-ae8515b545ef%2Fcontent" alt="A constellation of connected research" width="1254" height="1254"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A research paper looks like a self-contained unit. It is not. It cites earlier work, those papers cite others, and the idea you are reading about has been shaped by authors, institutions, and disciplines over years. A bibliography compresses all of that into a flat list at the back.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea: make the citation network navigable
&lt;/h2&gt;

&lt;p&gt;Citation data is public and structured - projects like OpenAlex index hundreds of millions of works and their references. The raw material for seeing a paper's neighborhood already exists. What is usually missing is the interface: a way to stand on one paper and look around.&lt;/p&gt;

&lt;p&gt;An evidence-map view changes the questions a researcher can ask. Instead of only "where is this paper?", you can ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What is this paper connected to?&lt;/li&gt;
&lt;li&gt;Which work influenced it?&lt;/li&gt;
&lt;li&gt;What other research sits around this idea?&lt;/li&gt;
&lt;li&gt;Where does this argument fit in the wider body of evidence?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These are not exotic needs. Literature reviews, replication checks, and defending a claim to a skeptical supervisor all reduce to navigating exactly this graph. Doing it by hand means dozens of tabs and a spreadsheet you abandon by week two.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trade-offs
&lt;/h2&gt;

&lt;p&gt;Citation graphs are easy to render and hard to make useful. More nodes and edges quickly become a hairball that looks impressive and answers nothing; the value is in ranking, filtering, and letting the reader expand from a paper outward in the direction they care about. Citation links also say nothing about &lt;em&gt;why&lt;/em&gt; something was cited - agreement, rebuttal, or ritual - so a map supports judgment; it does not replace reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  How we approached it at Andika
&lt;/h2&gt;

&lt;p&gt;Andika is a knowledge workspace with a research focus, and its new Evidence Map lets researchers explore a paper alongside the constellation of work connected to it: citation relationships, related papers, authors, communities, and the surrounding evidence. We built it on our own model over OpenAlex data, and the goal is practical: make research easier to navigate, understand, and defend.&lt;/p&gt;

&lt;p&gt;A bibliography is a list. A research constellation shows the thinking behind it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Adapted from a post on the &lt;a href="https://andika.work/blog/052a3854-83ff-4041-9c9a-8eee315cf759" rel="noopener noreferrer"&gt;Andika blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>research</category>
      <category>openscience</category>
      <category>data</category>
      <category>ai</category>
    </item>
    <item>
      <title>Let the workspace remember: knowledge graphs for your documents</title>
      <dc:creator>Boitumelo Phetla</dc:creator>
      <pubDate>Tue, 20 Oct 2026 13:09:00 +0000</pubDate>
      <link>https://dev.to/boitumelo_phetla_022b64a2/let-the-workspace-remember-knowledge-graphs-for-your-documents-41jm</link>
      <guid>https://dev.to/boitumelo_phetla_022b64a2/let-the-workspace-remember-knowledge-graphs-for-your-documents-41jm</guid>
      <description>&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%2Fandika.work%2Fapi%2Fpublic%2Fassets%2F1098dc90-15bc-4ef0-a207-188f16f6da10%2Fcontent" 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%2Fandika.work%2Fapi%2Fpublic%2Fassets%2F1098dc90-15bc-4ef0-a207-188f16f6da10%2Fcontent" alt="From assets to knowledge" width="1092" height="1440"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You come back to your desk after a week away and stare at three files: &lt;code&gt;final.pdf&lt;/code&gt;, &lt;code&gt;final_final.pdf&lt;/code&gt;, &lt;code&gt;final_final_v2.pdf&lt;/code&gt;. You created all of them. You have no idea what any of them mean anymore.&lt;/p&gt;

&lt;p&gt;That moment is a knowledge-management problem, and most tools answer it with search. Search helps when you remember the words. It fails when what you actually remember is the &lt;em&gt;relationship&lt;/em&gt;: the diagram that went with the proposal, the paper that backed up the argument, the note that explained why the file exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea: store relationships, not just documents
&lt;/h2&gt;

&lt;p&gt;A knowledge graph treats the things in a workspace as connected entities rather than isolated files. Documents link to their attachments, notebooks, projects, and sources. The links carry meaning, not just pointers.&lt;/p&gt;

&lt;p&gt;The interesting design question is where that meaning comes from. One option is inference: feed everything to a language model and let it rediscover context on demand. That works, but it re-derives what the author already knew, spends tokens doing it, and can misread the intent. Another option is to capture the relationship at the moment it exists. When someone attaches an image to a document, ask two cheap questions: what does this asset contain, and why is it attached here? Those two answers, recorded once, are worth more than a hundred re-analyses later.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trade-offs
&lt;/h2&gt;

&lt;p&gt;Explicit relationship capture adds friction to the writing flow, and relationships go stale as documents evolve. Inference scales without asking anything of the author but inherits the model's misunderstandings. Most real systems end up hybrid: automatic structure where it is reliable (this file belongs to this project), human-supplied context where it matters (this image is the &lt;em&gt;evidence&lt;/em&gt;, not decoration).&lt;/p&gt;

&lt;h2&gt;
  
  
  How we approached it at Andika
&lt;/h2&gt;

&lt;p&gt;Andika is a knowledge workspace, and its assets are not treated as files with filenames alone. When you attach something, the platform asks what it contains and why it is attached, creating an explicit relationship between the asset, the document, and your intent. Connected AI tools receive that grounding directly instead of re-inferring it, which cuts token usage and keeps human meaning at the center.&lt;/p&gt;

&lt;p&gt;On top of that, Andika automatically builds relationships between documents, pages, attachments, notebooks, and projects, so the workspace itself remembers what connects to what when your own memory fails. Under the hood we are building the AND model, intelligence designed to help you recall what you worked on and how your information connects. And if you do not want to wait for it, you can connect your own LLM: Andika's MCP layer gives it structured, read-only access to navigate the workspace with you.&lt;/p&gt;

&lt;p&gt;You should not have to remember where your knowledge lives. Your workspace should remember for you.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Adapted from posts on the &lt;a href="https://andika.work/blog/d0dbeb97-81c5-4f69-953c-b5c5711343d0" rel="noopener noreferrer"&gt;Andika blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>knowledgegraph</category>
      <category>ai</category>
      <category>architecture</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Provenance beats detection: recording AI's role in writing</title>
      <dc:creator>Boitumelo Phetla</dc:creator>
      <pubDate>Tue, 20 Oct 2026 12:02:42 +0000</pubDate>
      <link>https://dev.to/boitumelo_phetla_022b64a2/provenance-beats-detection-recording-ais-role-in-writing-mf0</link>
      <guid>https://dev.to/boitumelo_phetla_022b64a2/provenance-beats-detection-recording-ais-role-in-writing-mf0</guid>
      <description>&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%2Fandika.work%2Fapi%2Fpublic%2Fassets%2F81277ef5-ba6f-49ce-bccb-6c7e57c6fb06%2Fcontent" 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%2Fandika.work%2Fapi%2Fpublic%2Fassets%2F81277ef5-ba6f-49ce-bccb-6c7e57c6fb06%2Fcontent" alt="Grade my work" width="1182" height="1330"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;"Was this written by AI?" is the question schools, publishers, and teams keep asking, and the honest answer from detection tools is usually a shrug dressed up as a percentage. There is a more reliable approach available to anyone building writing software: stop guessing, and start recording.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea: provenance beats detection
&lt;/h2&gt;

&lt;p&gt;AI-text detectors try to infer authorship from statistical fingerprints in the final text. They are unreliable in both directions: they flag careful human writers and miss polished machine output. The deeper problem is conceptual. They are trying to reconstruct history from a single artifact.&lt;/p&gt;

&lt;p&gt;Revision provenance takes the opposite path. If the writing environment saves every revision and knows which saves involved AI assistance, then the history is not inferred at all. It is recorded. Questions like "what did this document look like before the AI pass?" or "which passages did the assistant rewrite?" become lookups, not guesses.&lt;/p&gt;

&lt;p&gt;That shift enables better features:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Contribution maps.&lt;/strong&gt; Show exactly which passages were rewritten, added, or removed in an AI-assisted revision, with before-and-after comparisons.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Honest attribution.&lt;/strong&gt; Instead of "this document is 37% AI" (a meaningless claim), you can say how much of the &lt;em&gt;measurable improvement&lt;/em&gt; happened during AI-assisted saves versus human ones.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Defensible records for students and researchers.&lt;/strong&gt; A writer can show their original questions, arguments, and groundwork existed before any AI refinement, and a supervisor can discuss writing development without relying on an opaque detector score.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The trade-offs
&lt;/h2&gt;

&lt;p&gt;Provenance only works inside the system that recorded it. Paste in text from outside and the chain breaks. It also requires the platform to be trustworthy about its own records, and it produces developmental analytics, not verdicts: a revision history cannot tell you whether assistance was &lt;em&gt;appropriate&lt;/em&gt;, only where it &lt;em&gt;happened&lt;/em&gt;. Treating it as a disciplinary instrument would repeat the detector mistake with better data.&lt;/p&gt;

&lt;h2&gt;
  
  
  How we approached it at Andika
&lt;/h2&gt;

&lt;p&gt;Andika, the knowledge workspace we are building, keeps a full revision history per document, and AI-assisted saves are marked as such in that history. Two features build on it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Document History&lt;/strong&gt; now includes a contribution map for AI-assisted revisions: passages rewritten, added, or removed, each with a before-and-after comparison you can inspect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Grade Me&lt;/strong&gt; compares the original draft with every saved revision and reports how language, structure, and ideas evolved: voice retention, semantic similarity, intent and entity preservation, lexical diversity, and reading-grade estimates. The report separates improvement from human revisions from improvement during recorded AI-assisted saves, and every AI-assisted revision is explainable: pick it on the timeline and see the exact passages that changed.&lt;/p&gt;

&lt;p&gt;We are deliberate about what this is not. It is not a plagiarism verdict, an institutional mark, or a claim that some percentage of a document "belongs to AI." It is developmental analytics, built on recorded history rather than text statistics. AI should not erase the author from the page; it should help the author see where their work became stronger.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Adapted from posts on the &lt;a href="https://andika.work/blog/6b9c3e7d-ce68-4f7f-a249-84fa69aba96f" rel="noopener noreferrer"&gt;Andika blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>writing</category>
      <category>ethics</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Discussing documents where the discussion belongs</title>
      <dc:creator>Boitumelo Phetla</dc:creator>
      <pubDate>Tue, 20 Oct 2026 11:00:45 +0000</pubDate>
      <link>https://dev.to/boitumelo_phetla_022b64a2/discussing-documents-where-the-discussion-belongs-2pm6</link>
      <guid>https://dev.to/boitumelo_phetla_022b64a2/discussing-documents-where-the-discussion-belongs-2pm6</guid>
      <description>&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%2Fandika.work%2Fapi%2Fpublic%2Fassets%2F4d307e50-badb-476f-9f45-356a08a27ad4%2Fcontent" 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%2Fandika.work%2Fapi%2Fpublic%2Fassets%2F4d307e50-badb-476f-9f45-356a08a27ad4%2Fcontent" alt="Research and documentation" width="685" height="841"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;A comment thread at the bottom of a long document is where feedback goes to lose its context. "This paragraph needs a source" forces the reader to hunt for which paragraph, on which page, in which version. The alternative is anchoring the discussion to the thing it discusses.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea: anchor conversations to content
&lt;/h2&gt;

&lt;p&gt;Most writing tools treat comments as a single stream attached to the document as a whole. Anchored annotation works differently: a discussion thread attaches to a specific section, sentence, or element. The context travels with the comment.&lt;/p&gt;

&lt;p&gt;This changes a few things in practice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Feedback becomes actionable.&lt;/strong&gt; The reader sees exactly what the comment refers to, and the writer can resolve threads one by one without archaeology.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Navigation and discussion reinforce each other.&lt;/strong&gt; If your document has a real section structure, anchored tags double as a map: jump to a section, see its open threads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Documents become reference material.&lt;/strong&gt; Attachments and related documents can live next to the work itself, at workspace or document level, instead of in a separate folder system people forget to check.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are design costs. Anchors break when the anchored text is deleted or heavily rewritten, so the system has to decide: orphan the thread, re-anchor it, or mark it stale. Fine-grained anchoring also raises the permission question: if you can hide a document from specific people, do its threads inherit that? (They should.)&lt;/p&gt;

&lt;h2&gt;
  
  
  How we approached it at Andika
&lt;/h2&gt;

&lt;p&gt;Andika is a knowledge workspace built around Markdown documents, and several recent additions came directly from user requests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A tagging system on top of documents.&lt;/strong&gt; You can tag a specific section and start a focused discussion thread on it, rather than collecting all feedback in one stream.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Document attachments at both workspace and document level&lt;/strong&gt;, with the option to hide specific documents from specific people.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Section navigation in the Markdown editor and preview&lt;/strong&gt;, plus copy buttons on code blocks in the preview, so reference documents are easier to move through and reuse.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reader summaries on the same page&lt;/strong&gt;, and the option to send a document to a large language model for summarization when you want a machine-generated first pass.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of these is a headline feature on its own. Together they move a document from "a page of text" toward "a place where reading, discussing, and referencing happen in one spot."&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Adapted from posts on the &lt;a href="https://andika.work/blog/bb22bea9-dae2-4a51-87cb-ae74cb574fc9" rel="noopener noreferrer"&gt;Andika blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>collaboration</category>
      <category>ux</category>
      <category>webdev</category>
    </item>
    <item>
      <title>OAuth sign-in: letting users bring an identity they already trust</title>
      <dc:creator>Boitumelo Phetla</dc:creator>
      <pubDate>Tue, 20 Oct 2026 09:52:52 +0000</pubDate>
      <link>https://dev.to/boitumelo_phetla_022b64a2/oauth-sign-in-letting-users-bring-an-identity-they-already-trust-1hll</link>
      <guid>https://dev.to/boitumelo_phetla_022b64a2/oauth-sign-in-letting-users-bring-an-identity-they-already-trust-1hll</guid>
      <description>&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%2Fandika.work%2Fapi%2Fpublic%2Fassets%2Feac1c1e4-f5bf-477b-b7d7-f2a7eb292b3c%2Fcontent" 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%2Fandika.work%2Fapi%2Fpublic%2Fassets%2Feac1c1e4-f5bf-477b-b7d7-f2a7eb292b3c%2Fcontent" alt="Sketch of OAuth sign-in" width="1158" height="1359"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every new product asks for a new password, and every new password is one more thing to forget, reuse, or lose. Delegated sign-in exists to break that pattern: instead of trusting every app with its own credential database, you let an identity provider you already use vouch for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idea: delegate authentication
&lt;/h2&gt;

&lt;p&gt;OAuth 2.0, usually paired with OpenID Connect for sign-in, lets an application ask a provider like Google, Microsoft, or Apple to authenticate the user on its behalf. The flow in brief:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The app redirects you to the provider.&lt;/li&gt;
&lt;li&gt;You sign in there (or are already signed in) and approve the request.&lt;/li&gt;
&lt;li&gt;The provider sends the app back a token asserting who you are.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The important property is what the app never sees: your password. The provider handles the credential, the second factor, the suspicious-login checks. The app receives a stable identity reference and builds its own profile on top of that.&lt;/p&gt;

&lt;p&gt;This changes where the security burden sits. A small team no longer has to store password hashes, build reset flows, and get credential-stuffing defense right. It inherits the provider's investment in account security instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trade-offs
&lt;/h2&gt;

&lt;p&gt;Delegated sign-in is not free:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You depend on the provider's availability and policies.&lt;/strong&gt; If Google sign-in has an outage, your users do too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Account linking is a design problem.&lt;/strong&gt; If someone signs up with email, then later clicks "Sign in with Google" using the same address, is that the same account? Getting this wrong creates duplicate identities or, worse, account takeover paths.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Institutional users bring their own identity systems.&lt;/strong&gt; Universities and enterprises often already run Microsoft Entra ID. Supporting only one provider means telling those teams to maintain a parallel identity just for your product.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Authentication is not authorization.&lt;/strong&gt; Proving who someone is says nothing about what they may access. That part stays in your application, and it is where most real access-control work lives.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How we approached it at Andika
&lt;/h2&gt;

&lt;p&gt;Andika, the knowledge workspace we are building, now supports Google, Microsoft and Apple sign-in alongside email and password. When someone signs in with Google, Google handles authentication and Andika never receives or stores the Google password. We create an application profile in our own database linked to the provider's stable identity reference, and manage workspaces, permissions, invitations and preferences on top of that.&lt;/p&gt;

&lt;p&gt;We rolled this out in stages: Google sign-in was configured and working first, while Microsoft Entra registration and Apple Developer enrolment were still in progress. Microsoft support mattered to us specifically because universities and enterprises, the organisations we want to serve, commonly manage identities through Entra ID already.&lt;/p&gt;

&lt;p&gt;One boundary we keep explicit: authentication stays separate from connected storage. Signing in with an external identity provider does not hand over your documents or infrastructure. Identity is verified before access is granted, and each organisation keeps control of where its information lives.&lt;/p&gt;

&lt;p&gt;Easier to enter, without weakening the parts that keep knowledge safe.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post is adapted from announcements on the &lt;a href="https://andika.work/blog/9560781c-47c8-4447-b552-8d53a9338c94" rel="noopener noreferrer"&gt;Andika blog&lt;/a&gt;. All product sketches are hand-drawn by Andika's founder on a tablet before rendering.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>oauth</category>
      <category>authentication</category>
      <category>security</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Putting an API gateway in front of everything</title>
      <dc:creator>Boitumelo Phetla</dc:creator>
      <pubDate>Tue, 20 Oct 2026 08:47:04 +0000</pubDate>
      <link>https://dev.to/boitumelo_phetla_022b64a2/putting-an-api-gateway-in-front-of-everything-4c17</link>
      <guid>https://dev.to/boitumelo_phetla_022b64a2/putting-an-api-gateway-in-front-of-everything-4c17</guid>
      <description>&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%2Fandika.work%2Fapi%2Fpublic%2Fassets%2F9312d8a8-fd58-434d-97b8-8eb831ef777f%2Fcontent" 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%2Fandika.work%2Fapi%2Fpublic%2Fassets%2F9312d8a8-fd58-434d-97b8-8eb831ef777f%2Fcontent" alt="API security" width="699" height="907"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a product grows past a handful of endpoints, API security stops being a feature of individual services and starts being an architectural question. Where do policies live? Who enforces them? How do you know the same rules apply to endpoint number fifty as to endpoint number five?&lt;/p&gt;

&lt;p&gt;Our answer, on Andika, was to put an API gateway in front of everything: Kong, running on top of Nginx, as the single entry layer for API traffic before it reaches our services.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a gateway actually buys you
&lt;/h2&gt;

&lt;p&gt;It's tempting to see a gateway as a fancy reverse proxy. The real value is centralisation of policy:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One enforcement point.&lt;/strong&gt; Authentication expectations, rate limits, request constraints, and access rules are applied consistently, before traffic touches application code. A service behind the gateway doesn't each have to reinvent its own perimeter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One place to change the rules.&lt;/strong&gt; When a policy needs tightening, you change it once. You don't go on a scavenger hunt through a dozen services hoping you found every copy of the logic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reduced blast radius for sloppy requests.&lt;/strong&gt; Malformed, unauthorized, or abusive traffic gets stopped at the edge, where it's cheap to reject, rather than deep in the system, where it's expensive.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The other half: visibility
&lt;/h2&gt;

&lt;p&gt;Blocking bad traffic is only half the job. The other half is seeing what's happening at all.&lt;/p&gt;

&lt;p&gt;We paired the gateway with monitoring across API activity and infrastructure behaviour, because policies without observation are just hopes. Watching traffic patterns at the edge is what lets you notice a credential-stuffing attempt as a shape in the data rather than as a postmortem. Earlier detection is the difference between "we rate-limited an odd burst" and "we found out from a user."&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest trade-off
&lt;/h2&gt;

&lt;p&gt;A gateway is also a single point of failure and a latency hop, and pretending otherwise is how architectures get cargo-culted. You take on operating it properly: redundancy, careful rollout of policy changes, and accepting that every request pays a small toll at the door.&lt;/p&gt;

&lt;p&gt;For us, the trade is worth it. Centralised, consistently applied policy plus real visibility beats a scattered set of per-service defences that drift out of sync with each other over time. Security posture you can reason about beats security posture you merely assume.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was adapted, with AI assistance, from &lt;a href="https://andika.work/blog/b3a40501-eb52-4dd3-88fc-df8ea73652db" rel="noopener noreferrer"&gt;an article on the Andika blog&lt;/a&gt;, where we build a collaborative writing and knowledge platform.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>api</category>
      <category>devops</category>
      <category>architecture</category>
    </item>
    <item>
      <title>Permissions that actually enforce, not just hide</title>
      <dc:creator>Boitumelo Phetla</dc:creator>
      <pubDate>Tue, 20 Oct 2026 08:46:30 +0000</pubDate>
      <link>https://dev.to/boitumelo_phetla_022b64a2/permissions-that-actually-enforce-not-just-hide-odn</link>
      <guid>https://dev.to/boitumelo_phetla_022b64a2/permissions-that-actually-enforce-not-just-hide-odn</guid>
      <description>&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%2Fandika.work%2Fapi%2Fpublic%2Fassets%2F3d68d9f2-3f07-4a8f-870e-da4951c9240b%2Fcontent" 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%2Fandika.work%2Fapi%2Fpublic%2Fassets%2F3d68d9f2-3f07-4a8f-870e-da4951c9240b%2Fcontent" alt="Access control" width="1170" height="1344"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There's a quiet failure mode in collaborative software: permissions that hide things in the interface but don't actually protect them.&lt;/p&gt;

&lt;p&gt;The document disappears from the sidebar, so everyone assumes it's private. Then someone pastes a direct link into chat, and it opens. The hiding was cosmetic. The authorization was never real.&lt;/p&gt;

&lt;p&gt;We recently reworked access control in Andika, and the guiding rule was simple: access has to be enforced everywhere the data can be reached, not just where it's displayed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Default open, precisely closed
&lt;/h2&gt;

&lt;p&gt;The collaboration model starts permissive on purpose. When you invite someone to a workspace, they can see and work in it. That's what makes sharing low-friction.&lt;/p&gt;

&lt;p&gt;The control comes from exceptions. Workspace owners and administrators can restrict individual notebooks, documents, and attached files, so sensitive research, contracts, or internal drafts stay protected without forcing you to create a whole separate workspace every time something confidential comes up.&lt;/p&gt;

&lt;p&gt;This "default open, precisely closed" shape matters. If privacy requires restructuring your entire workspace hierarchy, people won't use it, and the sensitive material ends up in the shared space anyway.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enforcement means every surface
&lt;/h2&gt;

&lt;p&gt;The part that does the actual work is unglamorous: every way a piece of content can be reached has to check the same rule. When someone is excluded from a document in Andika, that means it's gone from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;navigation and search results&lt;/li&gt;
&lt;li&gt;knowledge maps and analytics&lt;/li&gt;
&lt;li&gt;notifications and mentions&lt;/li&gt;
&lt;li&gt;direct links (the important one)&lt;/li&gt;
&lt;li&gt;revision history and discussions&lt;/li&gt;
&lt;li&gt;AI-assisted features that read workspace content&lt;/li&gt;
&lt;li&gt;attachments and live collaboration sessions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last category is easy to forget and dangerous to forget. An AI feature that cheerfully summarizes a document the requester can't read is an authorization bypass with good branding. Any feature that retrieves context on a user's behalf, whether it's search, an LLM integration, or an export, has to evaluate the same permissions as the page view.&lt;/p&gt;

&lt;p&gt;Revocation has to be just as thorough. If access is withdrawn while someone is mid-edit in a live collaboration session, the session closes immediately. A permission change that only takes effect "next login" is a permission change that didn't happen.&lt;/p&gt;

&lt;h2&gt;
  
  
  The takeaway
&lt;/h2&gt;

&lt;p&gt;If you build or buy collaborative software, the test isn't "does the hidden thing disappear from the menu?" It's "enumerate every path to the data and check the lock on each one." Links, APIs, exports, search indexes, AI retrieval, live sessions. The count of paths is always bigger than you first think.&lt;/p&gt;

&lt;p&gt;Privacy that lives only in the UI is a vibe. Privacy that lives in the data layer is a property.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was adapted, with AI assistance, from &lt;a href="https://andika.work/blog/691ba397-5988-437b-82a9-93a459f3f0be" rel="noopener noreferrer"&gt;an article on the Andika blog&lt;/a&gt;, where we build a collaborative writing and knowledge platform.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>webdev</category>
      <category>architecture</category>
      <category>programming</category>
    </item>
    <item>
      <title>Designing a read-only MCP server first: least privilege for AI context</title>
      <dc:creator>Boitumelo Phetla</dc:creator>
      <pubDate>Tue, 20 Oct 2026 08:46:17 +0000</pubDate>
      <link>https://dev.to/boitumelo_phetla_022b64a2/designing-a-read-only-mcp-server-first-least-privilege-for-ai-context-1dah</link>
      <guid>https://dev.to/boitumelo_phetla_022b64a2/designing-a-read-only-mcp-server-first-least-privilege-for-ai-context-1dah</guid>
      <description>&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%2Fandika.work%2Fapi%2Fpublic%2Fassets%2F809e3e8a-8a24-4749-82b4-e411b56c583d%2Fcontent" 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%2Fandika.work%2Fapi%2Fpublic%2Fassets%2F809e3e8a-8a24-4749-82b4-e411b56c583d%2Fcontent" alt="Your data is yours" width="1120" height="1404"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Giving an AI application access to your users' knowledge is a trust decision before it's an engineering decision.&lt;/p&gt;

&lt;p&gt;At Andika we recently shipped our first Model Context Protocol (MCP) connection, and we made a choice that shaped the whole design: the first release is read-only. Here's the reasoning, and the specific controls that fell out of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What MCP gives you, in one paragraph
&lt;/h2&gt;

&lt;p&gt;MCP is a standard interface between applications and LLM clients on one side, and systems that hold context and tools on the other. Depending on what the server exposes, a connected client can read resources, call tools, and take actions. Instead of copy-pasting documents into a chat window, a user can connect a tool to their workspace and let it work with their documents through a governed channel.&lt;/p&gt;

&lt;p&gt;The power is obvious. So is the risk: you've just built a machine-consumable pipe into people's private knowledge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why read-only first
&lt;/h2&gt;

&lt;p&gt;The protocol supports far more than reading, and that's exactly why we started with less. Write access is where the irreversible mistakes live. A connected application that can edit, publish, delete, or invite members can do real damage with a single bad call, and "the model did it" is not an acceptable incident report.&lt;/p&gt;

&lt;p&gt;So our first MCP release draws the line firmly: connected applications can discover workspaces, search documents, read pages, examine revisions, and retrieve asset knowledge. They cannot edit, publish, delete, invite members, or change permissions. That's a scoping decision we made for this release, not a limitation of MCP itself. Expanding the boundary later is a deliberate project, not a toggle.&lt;/p&gt;

&lt;h2&gt;
  
  
  The controls that made it shippable
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Named, expiring, revocable tokens.&lt;/strong&gt; A user creates a connection token for a specific application, with a name they'll recognise later ("my research assistant") and an expiry. The raw token is shown once and stored as a hash, the same discipline as API keys and passwords.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The connection acts as the user who created it.&lt;/strong&gt; Not as the platform, not as a superuser. It inherits exactly that user's permissions: organisation boundaries, workspace membership, group access, page and asset restrictions, deleted content, account status. If the user can't see it, the connection can't see it. Notably, even a platform administrator doesn't automatically get access to another organisation's private workspace, and the connection respects that too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A dedicated audit trail.&lt;/strong&gt; Every MCP operation is recorded. Sensitive search text is hashed rather than copied into audit records, because an audit log that leaks the very queries it protects is a liability wearing a costume.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revocation is a first-class feature.&lt;/strong&gt; The moment a connection feels wrong, the user kills it, and it dies completely.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What this means for ML tooling
&lt;/h2&gt;

&lt;p&gt;The interesting engineering question in LLM tooling right now isn't "how do we give the model more context?" It's "how do we give it the &lt;em&gt;right&lt;/em&gt; context, under governance the user can actually reason about?"&lt;/p&gt;

&lt;p&gt;A read-only, permission-inheriting, audited context channel is a foundation you can build on. When we do open write capabilities, the plan is granular scopes, explicit confirmation for consequential actions, optimistic revision checks so a stale client can't silently overwrite newer work, and a complete OAuth authorization flow.&lt;/p&gt;

&lt;p&gt;The broader lesson applies beyond MCP: treat every integration that touches user knowledge as a privilege negotiation, not a feature flag. Start with the smallest capability that delivers value, instrument it, and earn the right to expand.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was adapted, with AI assistance, from &lt;a href="https://andika.work/blog/b9242f31-6fc7-4877-8286-acb075d08032" rel="noopener noreferrer"&gt;an article on the Andika blog&lt;/a&gt;, where we build a collaborative writing and knowledge platform.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>mcp</category>
      <category>ai</category>
      <category>security</category>
      <category>llm</category>
    </item>
    <item>
      <title>Why we moved file storage out of PostgreSQL blobs into object storage</title>
      <dc:creator>Boitumelo Phetla</dc:creator>
      <pubDate>Tue, 20 Oct 2026 08:46:10 +0000</pubDate>
      <link>https://dev.to/boitumelo_phetla_022b64a2/why-we-moved-file-storage-out-of-postgresql-blobs-into-object-storage-5c1c</link>
      <guid>https://dev.to/boitumelo_phetla_022b64a2/why-we-moved-file-storage-out-of-postgresql-blobs-into-object-storage-5c1c</guid>
      <description>&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%2Fandika.work%2Fapi%2Fpublic%2Fassets%2F9da8e22b-921a-4d0d-8962-1fe300e4de51%2Fcontent" 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%2Fandika.work%2Fapi%2Fpublic%2Fassets%2F9da8e22b-921a-4d0d-8962-1fe300e4de51%2Fcontent" alt="Storage improvements" width="1185" height="1327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every side-project-turned-real-product has a moment where an early shortcut starts sending you invoices. Ours was storing uploaded files as blobs in PostgreSQL.&lt;/p&gt;

&lt;p&gt;It worked. It was simple. And then it started getting in the way.&lt;/p&gt;

&lt;p&gt;This is the story of why we moved Andika's file storage out of the database and into dedicated object storage, and what changed when we did.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shortcut that got us here
&lt;/h2&gt;

&lt;p&gt;When you're moving fast, putting file bytes in the database next to everything else is tempting. One system to back up, one connection to manage, no extra infrastructure. PostgreSQL will happily store binary data, and for a while the numbers are small enough that you never think about it.&lt;/p&gt;

&lt;p&gt;The problems arrive gradually:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The database becomes a file server.&lt;/strong&gt; Every upload and download is a round trip through the same engine that's supposed to be answering queries. Large files hold connections open and chew through memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backups get heavy.&lt;/strong&gt; Your careful backup strategy now includes gigabytes of immutable file bytes that never change but get carried along anyway.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scaling gets awkward.&lt;/strong&gt; You can scale a database, but you really don't want to scale it because someone uploaded a 40 MB PDF.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The data model lies to you.&lt;/strong&gt; A row that says &lt;code&gt;document_id, filename, bytes&lt;/code&gt; hides the truth: the bytes are not relational data. They're cargo.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this is PostgreSQL's fault. It's a brilliant database. It's just not a blob store.&lt;/p&gt;

&lt;h2&gt;
  
  
  What we moved to
&lt;/h2&gt;

&lt;p&gt;We migrated uploaded files and document assets to dedicated object storage (DigitalOcean Spaces, which speaks the S3 API). The division of labour is now clean:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Object storage holds the bytes.&lt;/strong&gt; PDFs, images, Word documents, presentations. Immutable, addressable, cheap to store, straightforward to cache and serve.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostgreSQL holds the truth about the bytes.&lt;/strong&gt; Metadata, provenance, ownership, workspace relationships, permissions, revision history.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every stored object is organised under unique organisation and workspace identifiers. That naming scheme prevents key collisions between tenants and keeps each customer's objects grouped under their own prefix. One thing worth being precise about: the prefix layout is organisation, not authorization. Who may read or write an object is still decided by the platform's permission checks on every request; the layout just means a bug or a bad guess at a key can't wander into another customer's namespace by accident.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed for us
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;The database went back to doing what it's good at: structured queries over structured information.&lt;/li&gt;
&lt;li&gt;File delivery no longer competes with query traffic.&lt;/li&gt;
&lt;li&gt;Database backups no longer carry immutable file bytes, and object storage has its own durability model.&lt;/li&gt;
&lt;li&gt;Capacity planning became two independent conversations instead of one panicked one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The lesson I'd hand to my earlier self
&lt;/h2&gt;

&lt;p&gt;"Boring" architectural decisions compound in both directions. Storing blobs in the database is a reasonable day-one choice and an expensive day-hundred choice. The earlier you separate bytes from metadata, the cheaper the separation is.&lt;/p&gt;

&lt;p&gt;It's the same principle behind a lot of good system design: put each kind of data in the place that's shaped like it. Files are shaped like object storage. Relationships are shaped like a relational database. Forcing one to be the other works right up until it really doesn't.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was adapted, with AI assistance, from posts on the &lt;a href="https://andika.work/blog/845779b0-ced2-4621-bd9f-faedba31b22e" rel="noopener noreferrer"&gt;Andika blog&lt;/a&gt;, where we build a collaborative writing and knowledge platform.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>postgres</category>
      <category>architecture</category>
      <category>storage</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Idempotency: the small idea that stops your app from doing things twice</title>
      <dc:creator>Boitumelo Phetla</dc:creator>
      <pubDate>Tue, 20 Oct 2026 08:45:59 +0000</pubDate>
      <link>https://dev.to/boitumelo_phetla_022b64a2/idempotency-the-small-idea-that-stops-your-app-from-doing-things-twice-phd</link>
      <guid>https://dev.to/boitumelo_phetla_022b64a2/idempotency-the-small-idea-that-stops-your-app-from-doing-things-twice-phd</guid>
      <description>&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%2Fandika.work%2Fapi%2Fpublic%2Fassets%2Ff9898bef-4b04-4cd5-a8e5-5caa595adb44%2Fcontent" 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%2Fandika.work%2Fapi%2Fpublic%2Fassets%2Ff9898bef-4b04-4cd5-a8e5-5caa595adb44%2Fcontent" alt="Idempotency" width="1084" height="1451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You hit "submit" on a form. The spinner turns. Nothing happens. You hit it again. And again.&lt;/p&gt;

&lt;p&gt;If the backend isn't designed for that moment, you just created three submissions. Or three charges. Or three identical comments on someone's post.&lt;/p&gt;

&lt;p&gt;The idea that prevents this is called idempotency, and it's one of those concepts that sounds academic until the day it saves your production database.&lt;/p&gt;

&lt;h2&gt;
  
  
  What idempotency actually means
&lt;/h2&gt;

&lt;p&gt;An operation is idempotent when performing it once has the same effect as performing it many times. The first request does the work; every identical retry produces no new effect.&lt;/p&gt;

&lt;p&gt;This matters because duplication pressure is normal behaviour, not an edge case. Users double-click and refresh. Networks time out mid-request, leaving the client unsure whether the server did the work. Load balancers, gateways, and retry policies in client libraries can all cause the same request to arrive more than once. If your API treats every arrival as a brand-new instruction, duplicates are a question of when, not if.&lt;/p&gt;

&lt;h2&gt;
  
  
  HTTP already gives you hints
&lt;/h2&gt;

&lt;p&gt;The HTTP verbs encode this idea:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;HEAD&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, and &lt;code&gt;DELETE&lt;/code&gt; are designed to be idempotent. Deleting a resource twice has the same end state as deleting it once: it's gone.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;POST&lt;/code&gt; is not idempotent by convention. Each &lt;code&gt;POST&lt;/code&gt; is, by default, a new "create" instruction.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That "by default" matters. A &lt;code&gt;POST&lt;/code&gt; endpoint can absolutely be designed to behave idempotently. That's exactly what the idempotency-key pattern does, and it's why the duplicate-submission problem almost always gets solved on the &lt;code&gt;POST&lt;/code&gt; endpoints: creating comments, processing payments, submitting assignments.&lt;/p&gt;

&lt;h2&gt;
  
  
  The idempotency-key pattern
&lt;/h2&gt;

&lt;p&gt;The standard fix for non-idempotent endpoints is the idempotency key:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The client generates a unique key for one logical operation (a UUID works fine).&lt;/li&gt;
&lt;li&gt;The client sends it with the request, usually as an &lt;code&gt;Idempotency-Key&lt;/code&gt; header.&lt;/li&gt;
&lt;li&gt;The server records the key when it processes the request and treats later arrivals of the same key as duplicates of the first, skipping the side effects.&lt;/li&gt;
&lt;li&gt;Many implementations also store the first response and replay it to the retrying client, so a retried request gets the original answer back instead of an error. Storing and replaying the response is a design choice on top of the core guarantee, not a requirement for idempotency itself, but it makes retries behave the way users intuitively expect.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Change anything meaningful about the request and it becomes a new operation with a new key. Stripe popularised this pattern for payments, and it's the reason retrying a payment request doesn't charge a customer twice.&lt;/p&gt;

&lt;p&gt;Implementation details worth thinking about:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enforce uniqueness with a database constraint, not application logic alone. Race conditions laugh at &lt;code&gt;if (!exists)&lt;/code&gt; checks.&lt;/li&gt;
&lt;li&gt;Scope keys per user or tenant so one user's key can never mask another's request.&lt;/li&gt;
&lt;li&gt;Give keys a sensible expiry. You need them long enough to cover realistic retry windows, not forever.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this matters beyond payments
&lt;/h2&gt;

&lt;p&gt;Once you see the pattern, you see the problem everywhere: webhook receivers, queue consumers (most queues guarantee at-least-once delivery, which means duplicates by design), sync engines, form submissions, file uploads.&lt;/p&gt;

&lt;p&gt;We ran into the submission version of this while building Andika. A reply or an assignment that a user submits should exist exactly once, no matter how many times the request arrives. So requests carry a unique key, the first one is processed, and matching duplicates are recognised and ignored. It's a small amount of code that removes an entire category of bugs and support tickets.&lt;/p&gt;

&lt;p&gt;For software engineering generally, idempotency is really about a mindset: assume every request may arrive more than once, and design the system so that's safe. Retries become harmless. Recovery becomes boring. Boring is good.&lt;/p&gt;

&lt;p&gt;For machine learning pipelines the same principle shows up in data ingestion and training jobs: reprocessing a batch should not double-count it. Deterministic keys and dedupe at the write boundary keep datasets honest.&lt;/p&gt;

&lt;p&gt;It's not glamorous. It's just correct.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;This post was adapted, with AI assistance, from &lt;a href="https://andika.work/blog/438fb87c-a72e-4730-aba4-c4291187117b" rel="noopener noreferrer"&gt;an article on the Andika blog&lt;/a&gt;, where we build a collaborative writing and knowledge platform.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>api</category>
      <category>programming</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
