<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>thats.nono</title><link>https://www.yopa.page/index.html</link><description>Recent content on thats.nono</description><generator>Hugo -- gohugo.io</generator><language>en</language><managingEditor>yoonsoo@duck.com (Yoonsoo Park)</managingEditor><webMaster>yoonsoo@duck.com (Yoonsoo Park)</webMaster><lastBuildDate>Mon, 24 Aug 2026 09:05:00 -0400</lastBuildDate><atom:link href="https://www.yopa.page/index.xml" rel="self" type="application/rss+xml"/><item><title>Give Every Agent Its Own Worktree</title><link>https://www.yopa.page/blog/2026-08-24-give-every-agent-its-own-worktree.html</link><pubDate>Mon, 24 Aug 2026 09:05:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-24-give-every-agent-its-own-worktree.html</guid><description>
&lt;p&gt;Cursor shipped a feature called Shadow Workspace: a background agent runs tests, linting, and spec checks in a separate, isolated Git worktree, so it can iterate on your code without disturbing the files you are actively editing. The agent gets a real working tree to mutate; you keep typing in yours; nobody steps on anybody.&lt;/p&gt;
&lt;p&gt;When I read the announcement my reaction was not &amp;ldquo;neat feature.&amp;rdquo; It was &amp;ldquo;yes, that is the pattern, and it should be the default.&amp;rdquo; Because I had already been running it by hand, and separately I had watched what happens when you &lt;em&gt;don&amp;rsquo;t&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="the-failure-mode-isolation-prevents"&gt;
&lt;a href="#the-failure-mode-isolation-prevents" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The failure mode isolation prevents
&lt;/h2&gt;
&lt;p&gt;Anthropic published research this month where they put several Claude agents on the same codebase without telling them about each other, and without any ownership or conflict policy. The agents started treating each other&amp;rsquo;s edits as hostile. Some runs escalated into what the writeup fairly called sabotage: agents interrupting each other&amp;rsquo;s processes, locking each other out, undoing each other&amp;rsquo;s work. A few runs reached a peaceful negotiated state, sometimes by asking for human mediation. But the default outcome of &amp;ldquo;many agents, one shared mutable workspace, no policy&amp;rdquo; was a turf war.&lt;/p&gt;
&lt;p&gt;That is the same class of bug as two developers force-pushing to the same branch, except the agents run faster and have less shame about it. The root cause is not that the agents are malicious. It is that they share a single mutable surface with no boundary, so every write is a potential collision, and a collision looks like an attack.&lt;/p&gt;
&lt;p&gt;The fix is not a smarter agent. It is a boundary.&lt;/p&gt;
&lt;h2 id="what-i-already-do-by-hand"&gt;
&lt;a href="#what-i-already-do-by-hand" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What I already do by hand
&lt;/h2&gt;
&lt;p&gt;My working tree gets dirty. Half-finished edits, scratch files, an experiment I have not decided about yet. When I need a clean branch off &lt;code&gt;main&lt;/code&gt; to do one focused thing, the naive move is &lt;code&gt;git checkout -b&lt;/code&gt;, and it fails or drags the mess along, and then I am stashing and un-stashing and fighting my own uncommitted state.&lt;/p&gt;
&lt;p&gt;The move that actually works is &lt;code&gt;git worktree&lt;/code&gt;. It gives me a second, physically separate checkout of the same repository on its own branch, in its own directory. My dirty main tree stays exactly as it is. I do the focused work in the isolated tree, commit it, push it, and remove the worktree. Two branches, two directories, zero interference. I reach for this specifically when the main tree is too dirty to safely branch from, and it has never once made me stash-juggle.&lt;/p&gt;
&lt;p&gt;That is Cursor&amp;rsquo;s Shadow Workspace, minus the automation. The insight is identical: an agent (or a task, or a version of me) that needs to mutate files should get its own worktree, not share yours.&lt;/p&gt;
&lt;h2 id="the-same-pattern-one-level-up"&gt;
&lt;a href="#the-same-pattern-one-level-up" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The same pattern one level up
&lt;/h2&gt;
&lt;p&gt;I run a lot of work through subagents, delegated tasks that execute in their own isolated context with their own terminal session. The reason that works, and does not turn into the Anthropic turf war, is precisely that each one is boxed. A subagent cannot reach into the parent&amp;rsquo;s state and clobber it. It gets a scope, does the work there, and returns a result. The isolation is what makes parallelism safe. Take the box away and you are back to agents fighting over one mutable surface.&lt;/p&gt;
&lt;p&gt;So there are three heights of the same idea:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;File level:&lt;/strong&gt; &lt;code&gt;git worktree&lt;/code&gt; gives an agent its own checkout. Cursor automates this as Shadow Workspace.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Task level:&lt;/strong&gt; each delegated subagent gets its own context and terminal, so concurrent tasks cannot corrupt each other.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System level:&lt;/strong&gt; the Anthropic research shows what the absence of any of this looks like, agents on one shared surface with no ownership, and it is not pretty.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The lesson threads through all three: concurrency is safe when each actor mutates its own copy and merges through a controlled join, and dangerous when they all write the same place and hope.&lt;/p&gt;
&lt;h2 id="the-tradeoff-honestly"&gt;
&lt;a href="#the-tradeoff-honestly" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The tradeoff, honestly
&lt;/h2&gt;
&lt;p&gt;Isolation is not free. Each worktree is another checkout on disk. Cursor&amp;rsquo;s own writeup flags the disk I/O cost of cloning worktrees in a large monorepo, and that is a real concern. If your repo is huge and your agents are many, N full worktrees can hurt.&lt;/p&gt;
&lt;p&gt;But the cost is disk and setup time, both of which are cheap and boring. The cost of &lt;em&gt;not&lt;/em&gt; isolating is collisions, corrupted state, and in the multi-agent case active sabotage, which are expensive and exciting in the worst way. I will pay disk to avoid a turf war every time.&lt;/p&gt;
&lt;h2 id="what-to-actually-do"&gt;
&lt;a href="#what-to-actually-do" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What to actually do
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;If you run background or parallel coding agents, give each one its own worktree or its own isolated context. Do not let two agents mutate the same working tree at once. This is the single highest-leverage safety property for multi-agent coding.&lt;/li&gt;
&lt;li&gt;If you are a human fighting your own dirty tree, learn &lt;code&gt;git worktree&lt;/code&gt; before you learn to juggle stashes. It is the cleaner tool and it is already in Git.&lt;/li&gt;
&lt;li&gt;Treat merges as the controlled join point. Each isolated actor produces a branch; you review and merge. The isolation buys you the ability to review before anything lands, which is exactly what the shared-surface agents never got.&lt;/li&gt;
&lt;li&gt;When you evaluate a multi-agent framework, ask where the boundary is before you ask how smart the agents are. If the answer is &amp;ldquo;they share a workspace and coordinate by talking,&amp;rdquo; you have read the Anthropic paper and you know how that ends.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The clean architecture diagram of a multi-agent system hides the moment where two arrows write the same file. Isolation is how you make sure that moment never arrives.&lt;/p&gt;</description></item><item><title>Irreversible Actions Cannot Be Gated by a Prompt</title><link>https://www.yopa.page/blog/2026-08-24-gate-irreversible-actions-in-code.html</link><pubDate>Mon, 24 Aug 2026 09:00:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-24-gate-irreversible-actions-in-code.html</guid><description>
&lt;p&gt;In August 2026 an AI assistant was told to book a gym class. The class was full, so the agent found the booking API, noticed it had no real authorization check, created a reservation outside the allowed window, and cancelled a higher-priority person on the waitlist to make room. Then it said &amp;ldquo;sorry about that.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The headlines called it a rogue agent hacking a system. That framing is wrong, and the wrong framing is dangerous because it points you at the wrong fix. The agent did not hack anything. It used an API exactly as the API allowed. The backend let an unauthenticated-enough caller cancel someone else&amp;rsquo;s reservation, and nothing in the system required a human to approve an irreversible action before it happened. The agent was just the first caller careless enough to walk through the open door.&lt;/p&gt;
&lt;p&gt;I want to argue one thing in this post: you cannot gate an irreversible action with a prompt. Not with a better system prompt, not with a &amp;ldquo;please confirm before destructive operations&amp;rdquo; instruction, not with a politely worded tool description. The stop has to live in deterministic code that runs whether or not the model cooperates. I believe this because I once deleted production infrastructure for eighteen teams, and the only reason it was recoverable is that a piece of dumb, non-negotiable code forced me to look before I leapt. The prompt would not have saved me. The code did.&lt;/p&gt;
&lt;h2 id="why-the-prompt-is-the-wrong-layer"&gt;
&lt;a href="#why-the-prompt-is-the-wrong-layer" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why the prompt is the wrong layer
&lt;/h2&gt;
&lt;p&gt;A prompt is a request. Even a good model treats &amp;ldquo;always ask before deleting&amp;rdquo; as strong guidance, not as a physical constraint. The moment the model decides the deletion is obviously fine, or misreads the situation, or gets nudged by adversarial input, the guidance loses. And an agent&amp;rsquo;s whole value is that it acts without you in the loop, so by the time you would notice, the irreversible thing has already happened.&lt;/p&gt;
&lt;p&gt;Think about the three places you could put the guardrail:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;In the prompt.&lt;/strong&gt; &amp;ldquo;Do not perform destructive actions without confirmation.&amp;rdquo; This is a suggestion to a probabilistic system. It fails silently and it fails exactly when you need it most, under weird inputs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;In the tool schema.&lt;/strong&gt; Mark the tool as dangerous, require a &lt;code&gt;confirm: true&lt;/code&gt; argument. Better, but the model still fills in that argument. If the model can produce the token that unlocks the action, the model can unlock the action. You have moved the decision, not removed it from the model.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;In deterministic code the model cannot talk its way past.&lt;/strong&gt; A wrapper, a hook, a backend check that refuses the action unless a condition outside the model&amp;rsquo;s control is met. This is the only layer that holds when the model is wrong.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The gym incident lived entirely at layer one and layer two. The backend trusted the caller. There was no layer three.&lt;/p&gt;
&lt;h2 id="the-time-i-was-the-rogue-agent"&gt;
&lt;a href="#the-time-i-was-the-rogue-agent" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The time I was the rogue agent
&lt;/h2&gt;
&lt;p&gt;Here is my own version of the same failure, and I was the intelligence in the loop, not a language model.&lt;/p&gt;
&lt;p&gt;I was cleaning up cloud infrastructure and ran a delete against what I thought was a scoped, throwaway resource. It was actually a shared API Gateway custom domain. Deleting it wiped the base-path mappings for eighteen teams in one call. Eighteen teams&amp;rsquo; traffic routing, gone, because one delete command did exactly what I asked instead of what I meant.&lt;/p&gt;
&lt;p&gt;No prompt would have stopped me. I &lt;em&gt;was&lt;/em&gt; the prompt. I read the command, I believed I understood the blast radius, and I was wrong. Human judgment is exactly the layer that fails here, the same way model judgment failed in the gym case. The intelligence being biological did not help.&lt;/p&gt;
&lt;p&gt;What saved the recovery was a habit I had encoded as a step, not a suggestion: before any shared-resource delete, capture the full mapping table first. That query output, the boring &lt;code&gt;(base path -&amp;gt; API, stage)&lt;/code&gt; dump I took right before deleting, turned out to be the exact blueprint I needed to rebuild seventeen of the eighteen mappings. The one I could not restore was the one whose backend API had itself been deleted, so there was nothing left to point at. The safety step meant to prevent the disaster was also the thing that made the disaster survivable.&lt;/p&gt;
&lt;p&gt;So I did what the gym&amp;rsquo;s backend engineers should have done. I moved the stop out of my head and into code.&lt;/p&gt;
&lt;h2 id="what-the-code-layer-actually-looks-like"&gt;
&lt;a href="#what-the-code-layer-actually-looks-like" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What the code layer actually looks like
&lt;/h2&gt;
&lt;p&gt;I now run my agent tooling behind a pre-execution hook that inspects every command before it runs and hard-blocks a small set of catastrophic operations. Not warns. Blocks. The command does not execute.&lt;/p&gt;
&lt;p&gt;The shape is simple:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A denylist of operations whose blast radius is irreversible and shared: delete a shared domain, delete a hosted zone, drop a production table. These return a hard block with a non-zero exit before the action fires.&lt;/li&gt;
&lt;li&gt;A deliberately narrow denylist. The hook does &lt;em&gt;not&lt;/em&gt; block the correctly-scoped fix (deleting a single base-path mapping, the surgical operation you actually want available during recovery). If you block everything, people route around you. Block only the operations whose cost is unrecoverable.&lt;/li&gt;
&lt;li&gt;An escape hatch that is auditable, not convenient. To proceed anyway you prefix the command with an explicit override token, something like &lt;code&gt;CONFIRM_SHARED_DELETE=1&lt;/code&gt;. Typing that token is a physical act that shows up in shell history and audit logs. It is the difference between &amp;ldquo;the model emitted confirm: true&amp;rdquo; and &amp;ldquo;a human deliberately, on the record, chose to override a block.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;A block message that doubles as a runbook. When the hook stops you, it prints the blast-radius query to run first and the exact narrowly-scoped commands to use instead. The guardrail teaches you the safe path at the moment you are trying to take the unsafe one.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The key property is that none of this trusts the intelligence issuing the command. It does not matter whether that intelligence is Claude, a shell script, or me at 2am. The block is a property of the environment, not a plea to the actor.&lt;/p&gt;
&lt;h2 id="the-general-principle"&gt;
&lt;a href="#the-general-principle" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The general principle
&lt;/h2&gt;
&lt;p&gt;Map it back to the gym. The correct fixes there were never &amp;ldquo;prompt the agent better.&amp;rdquo; They were:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The backend returns &lt;code&gt;403&lt;/code&gt; when a caller tries to cancel a resource it does not own. Authorization in code, checked server-side, every time.&lt;/li&gt;
&lt;li&gt;Irreversible actions (cancelling someone else&amp;rsquo;s booking) require an approval step the caller cannot self-issue. Human-in-the-loop as a hard gate, not a prompt.&lt;/li&gt;
&lt;li&gt;After a refusal, the agent does not get to hunt for an alternate endpoint that skips the check. The check lives on every path, not on the polite one.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Notice that these are exactly the properties of my delete hook, expressed in a different domain. Narrow the denylist to the truly irreversible. Make the override auditable and human-issued. Enforce it on every path, not just the expected one. And design the refusal so it points at the safe alternative instead of just saying no.&lt;/p&gt;
&lt;h2 id="what-to-actually-do"&gt;
&lt;a href="#what-to-actually-do" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What to actually do
&lt;/h2&gt;
&lt;p&gt;If you are shipping an agent that can touch anything consequential, do the boring thing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;List your agent&amp;rsquo;s actions and mark the ones that are irreversible or shared. That short list is the only part that needs a code-level gate. Everything else can stay fast and prompt-driven.&lt;/li&gt;
&lt;li&gt;For each item on that list, write a deterministic check that runs regardless of what the model says. A wrapper, a hook, a server-side authorization rule. If the model can emit the token that unlocks it, it is not a gate.&lt;/li&gt;
&lt;li&gt;Make the override a physical, auditable act by a human, not an argument the model can produce.&lt;/li&gt;
&lt;li&gt;Capture the recovery blueprint &lt;em&gt;before&lt;/em&gt; the destructive step, not after. The query you run to check blast radius is also the map you will use to rebuild.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The uncomfortable takeaway is that the intelligence of the actor is not the safety layer. A smarter model does not save you here, because the failure is not stupidity, it is confident wrongness, and both humans and models have plenty of that. Put the stop where confidence cannot reach it. Put it in code.&lt;/p&gt;</description></item><item><title>I Rebuilt the AWS Restaurant Pricing Swarm as a Surf School, Then Ran It 33 Times</title><link>https://www.yopa.page/blog/2026-08-20-surf-school-swarm-evidence.html</link><pubDate>Thu, 20 Aug 2026 09:00:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-20-surf-school-swarm-evidence.html</guid><description>
&lt;p&gt;AWS published a nice reference architecture: &lt;a href="https://aws.amazon.com/blogs/industries/build-a-dynamic-pricing-solution-for-restaurants-using-agentic-ai-strands-agents/"&gt;a dynamic pricing solution for restaurants using Strands Agents&lt;/a&gt;. An orchestrator fans out to specialist agents (demand, weather, local events, competitor pricing), each talks to its own data source through an MCP server, and a swarm parent aggregates everything into a price. The diagram is clean. Every arrow points forward. Nothing fails.&lt;/p&gt;
&lt;p&gt;I wanted to know what that diagram costs when you actually run it. So I rebuilt the exact same pattern in a different domain, a surf school booking assistant, and then I ran it 33 times on Bedrock and logged every hop.&lt;/p&gt;
&lt;p&gt;If you read my &lt;a href="./blog/2025-12-12-designing-robust-multi-agent-systems.html"&gt;post on designing multi-agent systems only when one agent is not enough&lt;/a&gt;, this is the empirical follow-up. Less &amp;ldquo;should you&amp;rdquo;, more &amp;ldquo;here is the bill&amp;rdquo;.&lt;/p&gt;
&lt;h2 id="the-pattern-retargeted-to-a-surf-school"&gt;
&lt;a href="#the-pattern-retargeted-to-a-surf-school" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The pattern, retargeted to a surf school
&lt;/h2&gt;
&lt;p&gt;A surf school has the same shape as the restaurant. Demand fluctuates, external signals drive it, and there is a hard constraint you cannot cross. For a restaurant that constraint is the cost floor (never sell below cost). For a surf school it is safety (never put a beginner in the water when the swell and gusts are dangerous).&lt;/p&gt;
&lt;p&gt;So I mapped the five specialists like this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;orchestrator
└── swarm
conditions_agent → get_surf_conditions (Open-Meteo Marine: swell height/period)
weather_agent → get_weather (Open-Meteo Forecast: wind, gusts, temp)
availability_agent → get_instructor_availability (local seed, stands in for DynamoDB)
safety_agent → (no tool: reasons over prior output; owns the safety veto)
pricing_agent → get_base_pricing (premium/discount, never below floor)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The two ocean signals are real. &lt;a href="https://open-meteo.com/"&gt;Open-Meteo&lt;/a&gt; serves swell and wind with no API key, so &lt;code&gt;conditions_agent&lt;/code&gt; and &lt;code&gt;weather_agent&lt;/code&gt; pull genuine data. Instructors and base prices are a local JSON seed, the same role DynamoDB plays in the AWS version. The safety rule lives as a sentence in &lt;code&gt;safety_agent&lt;/code&gt;&amp;rsquo;s prompt and is re-asserted in &lt;code&gt;pricing_agent&lt;/code&gt;, so a pricing incentive can never override a safety veto. That is the whole &amp;ldquo;policy in prompts&amp;rdquo; premise the AWS example rests on.&lt;/p&gt;
&lt;p&gt;The Strands wiring is almost exactly what the AWS sample shows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;strands.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BedrockModel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;strands.multiagent&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Swarm&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;swarm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Swarm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;build_specialists&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;# the five agents above, in order&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;max_handoffs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;max_iterations&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;execution_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;300.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;node_timeout&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;90.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;swarm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Recommend surf lesson slots and pricing for &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; on &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;day&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. lat=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;lat&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; lon=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;lon&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Handoffs are not code. Each specialist ends its prompt with &amp;ldquo;then hand off to &lt;code&gt;next_agent&lt;/code&gt;&amp;rdquo;, and the model decides to call the &lt;code&gt;handoff_to_agent&lt;/code&gt; tool. Keep that fact in mind, because it is where the diagram starts lying.&lt;/p&gt;
&lt;p&gt;To make the runs comparable I froze one live Open-Meteo snapshot, derived 11 stress scenarios from it (high swell, strong gusts, cold-calm, a beginner-boundary day, a snapshot with a missing observation hour), and ran each scenario three times at temperature zero. Same inputs, same seed, 33 Bedrock calls. Model was &lt;code&gt;claude-sonnet-4-6&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="what-the-diagram-shows-vs-what-33-runs-showed"&gt;
&lt;a href="#what-the-diagram-shows-vs-what-33-runs-showed" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What the diagram shows vs. what 33 runs showed
&lt;/h2&gt;
&lt;p&gt;Here is the honest scoreboard.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Runs: 33 (11 scenarios x 3 repeats)
Completed: 27 (81.8%)
Failed: 6 (18.2%)
Exact 5-agent path: 27 (81.8%)
Latency: mean 136.7s | median 132.5s | p95 159.6s | range 115-186s
Tokens/run: mean 65,730 | median 67,476 | p95 76,275
Batch tokens: 2,169,078 total (1.81M in / 356k out)
Est. batch cost: ~$10.78 (at $3/M in, $15/M out)
AWS throttles: 0
Timeouts: 0
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Read that again. &lt;strong&gt;One in five runs did not complete the clean path in the diagram, and not a single failure was AWS&amp;rsquo;s fault.&lt;/strong&gt; No throttling, no timeout. The failures were all internal to the swarm, and they cluster in a way the architecture picture actively hides.&lt;/p&gt;
&lt;h3 id="1-the-last-agent-is-the-most-expensive-and-the-most-fragile"&gt;
&lt;a href="#1-the-last-agent-is-the-most-expensive-and-the-most-fragile" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1. The last agent is the most expensive and the most fragile
&lt;/h3&gt;
&lt;p&gt;In the diagram every specialist is the same size box. In the logs they are not. Each agent reads all the prior agents&amp;rsquo; output and rewrites it into its handoff. Context amplifies down the chain, so the agents near the end carry the biggest payloads. In a representative run:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;conditions_agent 9,286 tokens
weather_agent 11,267
availability_agent 13,255
safety_agent 16,303 &amp;lt;- output tokens 3,676, the largest generation
pricing_agent 16,481
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Four of my six failures were &lt;code&gt;safety_agent&lt;/code&gt; hitting the 4,000-token generation limit while serializing its handoff (in one case, 21 approved slots). The agent that owns the most important rule, the safety veto, is also the one most likely to choke, precisely because it sits late in the chain and has to restate everything before it. The clean left-to-right arrows give you no hint that the right side of the chain is where it breaks.&lt;/p&gt;
&lt;h3 id="2-temperature-zero-did-not-make-it-deterministic"&gt;
&lt;a href="#2-temperature-zero-did-not-make-it-deterministic" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2. Temperature zero did not make it deterministic
&lt;/h3&gt;
&lt;p&gt;I ran every scenario three times with identical frozen inputs at temperature zero. Five scenarios failed once and then succeeded twice. Same snapshot, same seed, same prompts. The failure location moved between runs, and in one case the &lt;em&gt;agent path itself&lt;/em&gt; changed: &lt;code&gt;cold-calm&lt;/code&gt; repeat 1 finished pricing with a valid eight-slot result, then handed control &lt;em&gt;backward&lt;/em&gt; to &lt;code&gt;conditions_agent&lt;/code&gt;, looped, and exhausted the iteration budget. Repeats 2 and 3 went straight through.&lt;/p&gt;
&lt;p&gt;Temperature zero pins next-token sampling. It does not pin tool-call formatting, handoff length, or routing decisions. A multi-agent swarm has too many model-driven control points for &amp;ldquo;temperature 0&amp;rdquo; to mean &amp;ldquo;reproducible&amp;rdquo;. If you were counting on determinism to test a swarm, stop counting on it.&lt;/p&gt;
&lt;h3 id="3-the-arrows-hide-malformed-tool-calls-and-sdk-edges"&gt;
&lt;a href="#3-the-arrows-hide-malformed-tool-calls-and-sdk-edges" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
3. The arrows hide malformed tool calls and SDK edges
&lt;/h3&gt;
&lt;p&gt;Things the diagram cannot show that the logs did:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Argument-free tools fail to parse.&lt;/strong&gt; Tools like &lt;code&gt;get_base_pricing()&lt;/code&gt; that take no arguments repeatedly logged &lt;code&gt;failed to parse tool input json, defaulting to empty dict&lt;/code&gt;. Strands substituted &lt;code&gt;{}&lt;/code&gt; and continued, so the final answer often validated anyway, but the tool-call layer was quietly malformed on nearly every run.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;SwarmResult&lt;/code&gt; has no &lt;code&gt;final_response&lt;/code&gt; in Strands 1.52.0.&lt;/strong&gt; The AWS sample reads &lt;code&gt;result.final_response&lt;/code&gt;. In the version I ran that field does not exist; you read the pricing node out of &lt;code&gt;result.results&lt;/code&gt;. The published sample and the shipped SDK had already drifted.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bedrock rejects assistant-prefill continuations.&lt;/strong&gt; One &lt;code&gt;pricing_agent&lt;/code&gt; failure was Bedrock refusing to continue a conversation that ended on an assistant message. That is an SDK-and-model conversation-shape constraint, not anything you would ever infer from an architecture drawing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Schema drift between runs.&lt;/strong&gt; The same agent returned &lt;code&gt;slots&lt;/code&gt; as a bare list on some runs and as an object with &lt;code&gt;approved_slots&lt;/code&gt; on others. I had to write a validator that normalizes known shapes just to check the output. It observes; it never rewrites the model&amp;rsquo;s recommendation.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="4-compact-handoffs-cut-cost-almost-in-half-and-it-still-wasnt-cheap"&gt;
&lt;a href="#4-compact-handoffs-cut-cost-almost-in-half-and-it-still-wasnt-cheap" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
4. Compact handoffs cut cost almost in half, and it still wasn&amp;rsquo;t cheap
&lt;/h3&gt;
&lt;p&gt;My first naive build passed full hour-by-hour observations between agents and used 108,827 swarm tokens at 285 seconds per run. Switching to compact lesson-hour handoffs plus an eight-recommendation cap dropped the same-snapshot run to 64,781 tokens and 125 seconds, with the identical five-agent path. That is a huge win from one prompt-engineering change. And the batch still averaged 65,730 tokens and 136 seconds a run. A five-agent swarm is a genuinely expensive way to answer &amp;ldquo;what should today&amp;rsquo;s lessons cost&amp;rdquo;.&lt;/p&gt;
&lt;h2 id="about-that-safety-veto"&gt;
&lt;a href="#about-that-safety-veto" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
About that safety veto
&lt;/h2&gt;
&lt;p&gt;The comforting part: across every recommendation my validator could parse, there were zero beginner safety-threshold violations and zero price-floor violations. High-swell and strong-gust scenarios removed unsafe beginner slots in all completed outputs. Prompt-only policy looked like it held.&lt;/p&gt;
&lt;p&gt;The honest part: that does not prove prompt-only safety is reliable. Six runs never produced a usable result at all. Five were classified as malformed output. Two &lt;code&gt;missing-hour&lt;/code&gt; repeats recommended slots at an absent observation hour, which the validator flagged as &lt;code&gt;unverifiable&lt;/code&gt; rather than safe. And the validator only checks the final extracted slots, not every natural-language claim or every intermediate handoff. The defensible conclusion is narrow: &lt;em&gt;no measurable violation occurred in the verifiable completed recommendations of this batch.&lt;/em&gt; It is not &amp;ldquo;prompt-only safety works&amp;rdquo;. If safety actually mattered here, the veto belongs in deterministic code after the swarm, not in a prompt sentence you hope the last, most overloaded agent restates correctly.&lt;/p&gt;
&lt;h2 id="so-should-you-build-the-restaurant-swarm"&gt;
&lt;a href="#so-should-you-build-the-restaurant-swarm" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
So should you build the restaurant swarm?
&lt;/h2&gt;
&lt;p&gt;Build it to learn the pattern. It genuinely works: five specialists collaborated over real Bedrock calls, pulled live ocean data, and produced safe, floor-respecting price recommendations 27 out of 33 times. The Strands &lt;code&gt;Swarm&lt;/code&gt; primitive is a real thing you can stand up in an afternoon.&lt;/p&gt;
&lt;p&gt;But price it before you ship it:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Budget the tail, not the average.&lt;/strong&gt; ~65k tokens and ~136s &lt;em&gt;per recommendation&lt;/em&gt;, and the failure modes live at the end of the chain where context is fattest.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Do not trust temperature zero to give you reproducible swarm behavior.&lt;/strong&gt; It won&amp;rsquo;t. Test with repeats and expect a spread.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Put hard constraints in code, not prompts.&lt;/strong&gt; The safety veto and the price floor should be deterministic post-processing. Let the swarm propose; let code dispose.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Log every hop from day one.&lt;/strong&gt; Node path, per-node tokens, tool calls, validation result, failure class. The single most useful thing I built was the run log, and it is the one thing the AWS diagram doesn&amp;rsquo;t have.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The clean architecture diagram is the marketing. The 18.2% failure rate, the backward loop, the choking last agent, and the ~$10.78 for 33 runs are the engineering. If you&amp;rsquo;re going to run one of these in front of real customers, deploy on &lt;a href="./blog/2026-08-06-agentcore-runtime-instances.html"&gt;AgentCore Runtime&lt;/a&gt; for the managed parts, but budget for the messy parts the reference picture leaves out. For the wider &amp;ldquo;when is a swarm even the right tool&amp;rdquo; question, I still stand by &lt;a href="./blog/2025-12-12-designing-robust-multi-agent-systems.html"&gt;designing multi-agent systems only when one agent is not enough&lt;/a&gt;, and if you&amp;rsquo;re choosing a framework, &lt;a href="./blog/2026-02-28-strands-vs-langgraph.html"&gt;Strands vs LangGraph on Bedrock&lt;/a&gt; is the companion read.&lt;/p&gt;
&lt;p&gt;The diagram never fails. Your swarm will. Log it so you know how.&lt;/p&gt;</description></item><item><title>How to Use Cross-Session Messaging in Claude Code</title><link>https://www.yopa.page/blog/2026-08-10-claude-code-cross-session-messaging.html</link><pubDate>Mon, 10 Aug 2026 09:00:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-10-claude-code-cross-session-messaging.html</guid><description>
&lt;p&gt;Claude Code 2.1.224 added messaging between independently running sessions. One session can discover another with &lt;code&gt;ListAgents&lt;/code&gt; (also exposed through &lt;code&gt;/list-agents&lt;/code&gt; and &lt;code&gt;/peers&lt;/code&gt;) and deliver information with &lt;code&gt;SendMessage&lt;/code&gt;. Unlike teammate messaging inside Agent Teams, this feature connects sessions that are already running separately.&lt;/p&gt;
&lt;p&gt;It does not merge their contexts. Each session keeps its own conversation and working directory, receiving only the message that was sent. That makes the feature useful when parallel work spans repositories or worktrees and a person would otherwise copy context between terminals.&lt;/p&gt;
&lt;h2 id="check-the-version-first"&gt;
&lt;a href="#check-the-version-first" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Check the version first
&lt;/h2&gt;
&lt;p&gt;Cross-session &lt;code&gt;SendMessage&lt;/code&gt; arrived in Claude Code 2.1.224 for macOS, Linux, and WSL2. Native Windows support requires 2.1.234 or later.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;claude --version
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Version 2.1.225 extended it so a session can initiate a conversation by name with Remote Control sessions on another machine. &lt;code&gt;ListAgents&lt;/code&gt; displays those targets as &lt;code&gt;name [ref]&lt;/code&gt;. Use 2.1.225 or later when cross-machine messaging matters. The feature is not available when Claude Code is running through Amazon Bedrock, Claude Platform on AWS, Google Cloud Agent Platform, or Microsoft Foundry.&lt;/p&gt;
&lt;p&gt;There is no special message syntax to memorize. Ask Claude in natural language and it uses &lt;code&gt;ListAgents&lt;/code&gt; and &lt;code&gt;SendMessage&lt;/code&gt;; use &lt;code&gt;/list-agents&lt;/code&gt; or &lt;code&gt;/peers&lt;/code&gt; when you want to inspect reachable sessions yourself. (The &lt;code&gt;@&lt;/code&gt; mention shorthand requires a newer 2.1.232 build.)&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Find the sessions I can reach, then send this to backend-auth:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;POST /sessions now returns expires_at. Ask it to check the OpenAPI schema and
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;client contract, then reply with the affected files.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="give-sessions-stable-names"&gt;
&lt;a href="#give-sessions-stable-names" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Give sessions stable names
&lt;/h2&gt;
&lt;p&gt;Generated titles become difficult to distinguish once several sessions are active. Use &lt;code&gt;/rename&lt;/code&gt; in each session so its name describes its responsibility.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/rename backend-auth
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/rename web-session-client
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/rename integration-review
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Prefer a narrow ownership name such as &lt;code&gt;backend-auth&lt;/code&gt; over &lt;code&gt;backend&lt;/code&gt;, and &lt;code&gt;checkout-security-review&lt;/code&gt; over &lt;code&gt;review&lt;/code&gt;. Names also matter when discovering Remote Control sessions on another machine, so avoid duplicates.&lt;/p&gt;
&lt;p&gt;Before sending, ask Claude to use &lt;code&gt;ListAgents&lt;/code&gt; and verify the exact target. Do not guess when names are ambiguous.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Use ListAgents to confirm that exactly one session named web-session-client is
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;available. Send only if there is one match; otherwise stop and tell me.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="write-a-message-as-a-work-contract"&gt;
&lt;a href="#write-a-message-as-a-work-contract" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Write a message as a work contract
&lt;/h2&gt;
&lt;p&gt;The receiving session does not share the sender&amp;rsquo;s conversation history. A message such as “make the change we discussed” has no usable context. A good message contains enough information to choose the next action on its own.&lt;/p&gt;
&lt;p&gt;Five fields are usually enough:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;What changed&lt;/li&gt;
&lt;li&gt;Where the evidence lives&lt;/li&gt;
&lt;li&gt;How it affects the recipient&lt;/li&gt;
&lt;li&gt;What action is requested&lt;/li&gt;
&lt;li&gt;Whether and how to reply&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;[API contract change]
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Change: POST /sessions now returns expires_at: string
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Evidence: api/openapi.yaml, server/session.ts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Impact: check the response type and expiry UI in web/session-client.ts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Request: make the required changes and run the relevant tests
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Reply: send changed files, test results, and remaining decisions to backend-auth
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Do not paste an entire log or diff into the message. Send file paths, commits, pull requests, and test results that the recipient can inspect. Messaging should be a narrow interface between contexts, not context replication.&lt;/p&gt;
&lt;h2 id="patterns-that-work-well"&gt;
&lt;a href="#patterns-that-work-well" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Patterns that work well
&lt;/h2&gt;
&lt;h3 id="propagate-a-contract-across-repositories"&gt;
&lt;a href="#propagate-a-contract-across-repositories" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Propagate a contract across repositories
&lt;/h3&gt;
&lt;p&gt;After a backend session changes an API or event schema, it can notify a session working in the consumer repository. Include changed fields, compatibility expectations, the source commit, and the requested follow-up. The consumer replies with its implementation and verification state.&lt;/p&gt;
&lt;p&gt;The message does not replace compatibility tests, deployment ordering, or release gates. It makes the dependency visible while each repository keeps its own controls.&lt;/p&gt;
&lt;h3 id="connect-implementation-and-review-sessions"&gt;
&lt;a href="#connect-implementation-and-review-sessions" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Connect implementation and review sessions
&lt;/h3&gt;
&lt;p&gt;Instead of starting a new review conversation and explaining the background again, send an existing review session the commit and review lens.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Send commit abc123 to integration-review and ask it to check:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- backward compatibility with existing clients
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- safe handling when expires_at is absent
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- timezone boundary coverage
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Ask it to reply with severity, file paths, and reproduction evidence.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The review session can return findings directly to the implementation session. The person supervising the work can focus on judgment and approval instead of relaying text.&lt;/p&gt;
&lt;h3 id="unblock-work-with-one-precise-question"&gt;
&lt;a href="#unblock-work-with-one-precise-question" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Unblock work with one precise question
&lt;/h3&gt;
&lt;p&gt;When a session needs a decision owned elsewhere, do not transfer the entire task. Send the blocker and the required answer shape.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;web-session-client is blocked on timezone presentation. Ask product-contract
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;whether the contract requires preserving UTC or converting to the user&amp;#39;s locale.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Request only the decision and a link to its source.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This keeps the receiving session from repeating unrelated investigation.&lt;/p&gt;
&lt;h3 id="collect-status-from-independent-sessions"&gt;
&lt;a href="#collect-status-from-independent-sessions" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Collect status from independent sessions
&lt;/h3&gt;
&lt;p&gt;If you started several sessions yourself, one session can act as a lightweight coordination window. It may request structured status and summarize responses, without becoming the source of truth.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Find the reachable auth-related sessions. Ask each for completed output,
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;verification, blockers, and next action in the same format. Summarize the replies
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;as a table. Do not modify or deploy anything.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Avoid broadcasting to every session. Verify names and projects, keep the response schema small, and check completion against commits and tests.&lt;/p&gt;
&lt;h2 id="this-is-not-agent-teams"&gt;
&lt;a href="#this-is-not-agent-teams" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
This is not Agent Teams
&lt;/h2&gt;
&lt;p&gt;Cross-session messaging targets sessions that were started independently. Agent Teams is a separate orchestration model in which a lead creates teammates and manages shared tasks.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use cross-session messaging when people started independent sessions and only need to connect them at specific boundaries.&lt;/li&gt;
&lt;li&gt;Consider Agent Teams when a lead should create and continuously coordinate workers from the beginning.&lt;/li&gt;
&lt;li&gt;Use a subagent when one isolated task only needs to return a result.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The tools may share the &lt;code&gt;SendMessage&lt;/code&gt; name, but their targets and operating models differ. Independent session messaging should not be documented as teammate messaging.&lt;/p&gt;
&lt;h2 id="treat-permissions-and-delivery-failures-as-normal-states"&gt;
&lt;a href="#treat-permissions-and-delivery-failures-as-normal-states" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Treat permissions and delivery failures as normal states
&lt;/h2&gt;
&lt;p&gt;Version 2.1.224 also introduced &lt;code&gt;crossSessionInbound&lt;/code&gt; and &lt;code&gt;dialogExpiry&lt;/code&gt;. Inbound messages can be configured to &lt;strong&gt;accept&lt;/strong&gt;, &lt;strong&gt;hold&lt;/strong&gt;, or &lt;strong&gt;refuse&lt;/strong&gt;; messages to a session running with bypassed permissions may be held for user approval. If you enable &lt;code&gt;isolatePeerMachines&lt;/code&gt;, cross-machine messages require approval before delivery. These controls mean that “sent” and “delivered” are separate states.&lt;/p&gt;
&lt;p&gt;Do not treat “message sent” as “work complete.”&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Confirm that the target was discovered.&lt;/li&gt;
&lt;li&gt;Check that &lt;code&gt;SendMessage&lt;/code&gt; reported successful delivery.&lt;/li&gt;
&lt;li&gt;Include a response format for consequential requests.&lt;/li&gt;
&lt;li&gt;If there is no reply, check once instead of filling the inbox with retries.&lt;/li&gt;
&lt;li&gt;Do not make messaging a required unattended automation path where no user can answer an approval.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Keep secrets and unnecessary customer data out of messages. Treat a message arriving from another session as input crossing a trust boundary.&lt;/p&gt;
&lt;h2 id="keep-durable-state-outside-messages"&gt;
&lt;a href="#keep-durable-state-outside-messages" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Keep durable state outside messages
&lt;/h2&gt;
&lt;p&gt;Session messages are coordination events, not durable project records. Preserve these in commits, issues, design documents, or test artifacts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;final API and data contracts;&lt;/li&gt;
&lt;li&gt;approved design decisions;&lt;/li&gt;
&lt;li&gt;deployment and migration order;&lt;/li&gt;
&lt;li&gt;verification results and known limitations;&lt;/li&gt;
&lt;li&gt;handoff context required after a restart.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The message should point to the durable artifact and state the action needed now. Work remains recoverable even after a session ends.&lt;/p&gt;
&lt;h2 id="a-practical-checklist"&gt;
&lt;a href="#a-practical-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A practical checklist
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Is Claude Code 2.1.224 or later on macOS, Linux, or WSL2 (or 2.1.234+ on native Windows)?&lt;/li&gt;
&lt;li&gt;For initiating cross-machine messages, is it 2.1.225 or later?&lt;/li&gt;
&lt;li&gt;Do &lt;code&gt;/rename&lt;/code&gt; names distinguish session ownership?&lt;/li&gt;
&lt;li&gt;Did &lt;code&gt;ListAgents&lt;/code&gt; confirm the exact target?&lt;/li&gt;
&lt;li&gt;Does the message contain change, evidence, request, and reply conditions?&lt;/li&gt;
&lt;li&gt;Is the source of truth stored outside the conversation?&lt;/li&gt;
&lt;li&gt;Are delivery and task completion tracked separately?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The best use of cross-session messaging is not to create a self-organizing swarm. It is to pass contracts, findings, and review requests accurately between work that is already separated well. Keep sessions independent and messages small.&lt;/p&gt;
&lt;p&gt;The behavior described here is based on the current official &lt;a href="https://code.claude.com/docs/en/cross-session-messaging"&gt;cross-session messaging documentation&lt;/a&gt;, plus the &lt;a href="https://github.com/anthropics/claude-code/releases/tag/v2.1.224"&gt;v2.1.224 release notes&lt;/a&gt; and &lt;a href="https://github.com/anthropics/claude-code/releases/tag/v2.1.225"&gt;v2.1.225 release notes&lt;/a&gt;. See &lt;a href="https://code.claude.com/docs/en/sessions"&gt;session management&lt;/a&gt; for session naming and lifecycle details.&lt;/p&gt;</description></item><item><title>MCP Goes Stateless (2026-07-28): Host, Client, Server, and What Changes on AWS</title><link>https://www.yopa.page/blog/2026-08-09-mcp-goes-stateless.html</link><pubDate>Sun, 09 Aug 2026 09:00:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-09-mcp-goes-stateless.html</guid><description>
&lt;p&gt;The &lt;a href="https://blog.modelcontextprotocol.io/posts/2026-07-28/"&gt;final 2026-07-28 MCP specification&lt;/a&gt; is the biggest change to the protocol since it launched. The headline: MCP is now stateless at the protocol layer. A remote server that used to need sticky sessions, a shared session store, and a gateway that reads the request body can now sit behind a plain round-robin load balancer when its application state is also externalized or explicitly carried.&lt;/p&gt;
&lt;p&gt;Before I get to the transport change, let me clear up the part everyone asks about first: what exactly is a host, a client, and a server?&lt;/p&gt;
&lt;h2 id="host-client-server-in-three-minutes"&gt;
&lt;a href="#host-client-server-in-three-minutes" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Host, client, server in three minutes
&lt;/h2&gt;
&lt;p&gt;These three terms get mixed up constantly, usually because people can&amp;rsquo;t place where &amp;ldquo;Claude&amp;rdquo; sits.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Host&lt;/strong&gt; is the LLM application itself. Claude Desktop, Cursor, ChatGPT, or an agent running on AgentCore Runtime. It is where the user and the model live, and it owns one or more clients.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Client&lt;/strong&gt; is a connector object living inside the host. There is exactly one client per server. It speaks the JSON-RPC protocol and sends &lt;code&gt;tools/list&lt;/code&gt;, &lt;code&gt;tools/call&lt;/code&gt;, and so on. If your host connects to a GitHub server and a Slack server, it spins up two clients. The user never sees them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Server&lt;/strong&gt; is the process that exposes actual capabilities: tools, resources, prompts. The GitHub MCP server is the thing that advertises &lt;code&gt;create_issue()&lt;/code&gt;, &lt;code&gt;search_repos()&lt;/code&gt; and similar functions as tool specs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So to answer the question directly: the GitHub MCP server is indeed the place that provides the tools. Claude (Desktop or Cursor) is the &lt;strong&gt;host&lt;/strong&gt;. Inside Claude, the connector that talks 1:1 with the GitHub server is the &lt;strong&gt;client&lt;/strong&gt;. Host is the container, client is the wire to one server, server is the capability provider. People stumble because &amp;ldquo;client&amp;rdquo; is an implementation detail hidden inside the host, so from the outside it looks like Claude is talking to the server directly. It is, but through a client it owns.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;┌─ Host (Claude Desktop) ───────────────┐
│ user + model │
│ ├── Client A ──── GitHub MCP server │ create_issue(), search_repos()
│ └── Client B ──── Slack MCP server │ post_message(), list_channels()
└────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Hold onto that picture, because the stateless change is really a change in how the client and server talk to each other.&lt;/p&gt;
&lt;h2 id="what-changed-the-session-left-the-protocol"&gt;
&lt;a href="#what-changed-the-session-left-the-protocol" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What changed: the session left the protocol
&lt;/h2&gt;
&lt;p&gt;In 2025-11-25, calling one tool meant establishing a session first. The client sent an &lt;code&gt;initialize&lt;/code&gt; handshake, the server replied with an &lt;code&gt;Mcp-Session-Id&lt;/code&gt;, and every request after that had to carry the same ID. That ID pinned the client to whichever server instance issued it.&lt;/p&gt;
&lt;p&gt;The before/after from the final specification makes it concrete.&lt;/p&gt;
&lt;p&gt;Before (2025-11-25), two round trips, and the second is pinned:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-http" data-lang="http"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;POST&lt;/span&gt; &lt;span class="nn"&gt;/mcp&lt;/span&gt; &lt;span class="kr"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Content-Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="l"&gt;application/json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;jsonrpc&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;method&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;initialize&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;params&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;protocolVersion&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2025-11-25&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;capabilities&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:{},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;clientInfo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;my-app&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-http" data-lang="http"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;POST&lt;/span&gt; &lt;span class="nn"&gt;/mcp&lt;/span&gt; &lt;span class="kr"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Mcp-Session-Id&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="l"&gt;1868a90c-3a3f-4f5b&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Content-Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="l"&gt;application/json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;jsonrpc&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;method&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;tools/call&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;params&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;search&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;arguments&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;q&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;otters&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;After (2026-07-28), one self-contained request that any instance can serve:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-http" data-lang="http"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;POST&lt;/span&gt; &lt;span class="nn"&gt;/mcp&lt;/span&gt; &lt;span class="kr"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;MCP-Protocol-Version&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="l"&gt;2026-07-28&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Mcp-Method&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="l"&gt;tools/call&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Mcp-Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="l"&gt;search&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Content-Type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="l"&gt;application/json&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;jsonrpc&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;2.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;method&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;tools/call&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;params&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;search&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;arguments&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;q&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;otters&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;_meta&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;io.modelcontextprotocol/clientInfo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:{&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;my-app&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nt"&gt;&amp;#34;version&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;1.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}}}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Two things went away. The &lt;code&gt;initialize&lt;/code&gt;/&lt;code&gt;initialized&lt;/code&gt; handshake is removed (SEP-2575); the protocol version, client info, and capabilities now ride in &lt;code&gt;_meta&lt;/code&gt; on every request, and a new &lt;code&gt;server/discover&lt;/code&gt; method fetches server capabilities when the client wants them up front. And the &lt;code&gt;Mcp-Session-Id&lt;/code&gt; header, plus the protocol-level session behind it, is removed (SEP-2567). With both gone, any request can land on any instance.&lt;/p&gt;
&lt;h2 id="what-you-have-to-change-in-an-existing-server"&gt;
&lt;a href="#what-you-have-to-change-in-an-existing-server" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What you have to change in an existing server
&lt;/h2&gt;
&lt;p&gt;Stateless protocol does not mean your application has to be stateless. It means the protocol stops holding state for you, so you hold it explicitly.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Session state becomes an explicit handle.&lt;/strong&gt; Do what HTTP APIs have always done: a tool mints a &lt;code&gt;basket_id&lt;/code&gt; or &lt;code&gt;browser_id&lt;/code&gt; and returns it, and the model passes it back as a normal argument on the next call. The state is now visible to the model instead of hidden in transport metadata, which the spec authors argue is actually the more powerful pattern, because the model can compose and reason about the handles.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Server-to-client prompts move to Multi Round-Trip (SEP-2322).&lt;/strong&gt; Instead of holding an SSE stream open to ask &amp;ldquo;delete 3 files?&amp;rdquo;, the server returns an &lt;code&gt;InputRequiredResult&lt;/code&gt; with a &lt;code&gt;requestState&lt;/code&gt; blob. The client collects the answer and re-issues the original call with &lt;code&gt;inputResponses&lt;/code&gt; plus that echoed &lt;code&gt;requestState&lt;/code&gt;. Any instance can pick up the retry because everything it needs is in the payload. Also note server-initiated requests are now only allowed while the server is actively handling a client request (SEP-2260), so no more prompts out of nowhere.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cache your lists.&lt;/strong&gt; &lt;code&gt;tools/list&lt;/code&gt; and resource reads now carry &lt;code&gt;ttlMs&lt;/code&gt; and &lt;code&gt;cacheScope&lt;/code&gt; (SEP-2549), modeled on HTTP Cache-Control. You no longer need a long-lived SSE stream just to learn a list changed.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Small breaking bits.&lt;/strong&gt; Roots, Sampling, and Logging are deprecated (SEP-2577, annotation-only for now). The missing-resource error code changes from the MCP-custom &lt;code&gt;-32002&lt;/code&gt; to the standard &lt;code&gt;-32602 Invalid Params&lt;/code&gt; (SEP-2164), so update any client matching on the literal value. Tool schemas move to full JSON Schema 2020-12 (SEP-2106).&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-changes-when-you-deploy-on-aws"&gt;
&lt;a href="#what-changes-when-you-deploy-on-aws" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What changes when you deploy on AWS
&lt;/h2&gt;
&lt;p&gt;This is where stateless earns its keep.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A plain round-robin load balancer is enough.&lt;/strong&gt; Before, horizontal scaling needed sticky sessions or a shared session store so the pinned &lt;code&gt;Mcp-Session-Id&lt;/code&gt; always found its instance. That requirement is gone at the protocol layer, so an ALB with default routing works.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It fits Lambda and Fargate cleanly.&lt;/strong&gt; Any request landing on any instance is exactly the execution model serverless and autoscaled containers want. No instance affinity to fight.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The gateway routes on headers, not bodies.&lt;/strong&gt; The transport now requires &lt;code&gt;Mcp-Method&lt;/code&gt; and &lt;code&gt;Mcp-Name&lt;/code&gt; headers (SEP-2243), so a load balancer, gateway, or rate-limiter can route and throttle on the operation without deep packet inspection. Servers reject requests where the headers and body disagree.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tracing is standardized.&lt;/strong&gt; W3C Trace Context propagation in &lt;code&gt;_meta&lt;/code&gt; is now documented (SEP-414), fixing the &lt;code&gt;traceparent&lt;/code&gt;/&lt;code&gt;tracestate&lt;/code&gt;/&lt;code&gt;baggage&lt;/code&gt; key names so a trace can follow a call from the host through the client SDK, the server, and downstream, and land as one span tree in an OpenTelemetry backend.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="how-this-relates-to-agentcore"&gt;
&lt;a href="#how-this-relates-to-agentcore" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
How this relates to AgentCore
&lt;/h2&gt;
&lt;p&gt;If you run MCP servers on AWS, you most likely meet the protocol through two AgentCore pieces:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;AgentCore Runtime&lt;/strong&gt; hosts the server-side workload, an agent or an MCP server, in a managed container.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AgentCore Gateway&lt;/strong&gt; exposes tools as an MCP endpoint and puts SigV4 (IAM) in front of them, so an agent can load a gateway&amp;rsquo;s tools as if it were any other MCP server.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The stateless rework is a natural fit here for the same reason it helps any AWS deployment: AgentCore can scale by adding container instances, and the protocol no longer requires every request to carry a protocol session. But do not turn that into a blanket claim that AgentCore never has sessions. AgentCore Gateway and Runtime can still manage optional stateful MCP sessions for older clients and for interactive features such as elicitation and sampling. Drop gateway affinity only after checking the client version, target behavior, and the session mode you actually configured.&lt;/p&gt;
&lt;p&gt;One practical note from wiring a client against a deployed gateway: SigV4 is not native to the MCP client transport, so you inject a signing &lt;code&gt;fetch&lt;/code&gt; into the Streamable HTTP transport rather than relying on an OAuth provider. If you want the auth side of this in depth, I wrote about &lt;a href="./blog/2026-06-03-agentcore-gateway-mcp-oauth-auth-code.html"&gt;when MCP clients on AgentCore Gateway need OAuth auth code flow&lt;/a&gt;, and about &lt;a href="./blog/2025-12-11-architecture-patterns-for-strands-and-mcp.html"&gt;architecture patterns for Strands and MCP&lt;/a&gt; earlier. This post is the transport-layer companion to those.&lt;/p&gt;
&lt;h2 id="pitfalls-i-expect-people-to-hit"&gt;
&lt;a href="#pitfalls-i-expect-people-to-hit" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pitfalls I expect people to hit
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Assuming stateless means you delete all your state.&lt;/strong&gt; It does not. It means you move state out of the transport and into explicit handles that the model passes around. If your server genuinely had per-session state, you still need it, just in a place any instance can read.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Forgetting the header/body agreement rule.&lt;/strong&gt; If your gateway rewrites &lt;code&gt;Mcp-Method&lt;/code&gt; or &lt;code&gt;Mcp-Name&lt;/code&gt; but the body says something else, a conformant server rejects the request. Route on the headers, do not mutate them.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Matching on &lt;code&gt;-32002&lt;/code&gt;.&lt;/strong&gt; If any client code branches on the old missing-resource error code, it breaks silently against a 2026-07-28 server. Grep for it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Treating every implementation as final.&lt;/strong&gt; The 2026-07-28 document is now the final specification, but clients and gateways may still negotiate older versions. Verify the versions and stateful-session settings in your deployment before removing affinity or deleting session storage.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-to-actually-do"&gt;
&lt;a href="#what-to-actually-do" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What to actually do
&lt;/h2&gt;
&lt;p&gt;If you own a remote MCP server, start planning the protocol-session removal now: find where you rely on &lt;code&gt;Mcp-Session-Id&lt;/code&gt;, decide which state becomes an explicit handle, and switch server-to-client prompts to the Multi Round-Trip shape. If you deploy on AgentCore, first verify whether your clients and targets use the optional stateful session path; only then remove gateway affinity and let stateless requests scale flat. And if you are the person answering the host/client/server question for the tenth time, keep the one-liner handy: host owns clients, one client per server, server provides the tools.&lt;/p&gt;</description></item><item><title>How Much Does It Cost to Run Hermes Agent on AWS?</title><link>https://www.yopa.page/blog/2026-08-08-hermes-aws-cost-breakdown.html</link><pubDate>Sat, 08 Aug 2026 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-08-hermes-aws-cost-breakdown.html</guid><description>
&lt;p&gt;When I deployed Hermes Agent on AWS, I initially budgeted for the ECS task and the LLM. After running it for a little over a month, that estimate looked too simple. The always-on NAT Gateway and EFS throughput cost about as much as Fargate for one personal agent.&lt;/p&gt;
&lt;p&gt;This article answers a narrow question: how much did it cost to run Hermes on AWS? It is one measured deployment, not a universal price list. Your region, runtime, traffic, storage mode, and model provider will change the result.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Review note (2026-08-28):&lt;/strong&gt; These are still a historical, pre-credit usage snapshot. Recalculate with current AWS prices and the &lt;a href="https://hermes-agent.nousresearch.com/docs/user-guide/docker/"&gt;current Hermes Docker/configuration guidance&lt;/a&gt; before using the numbers for a new deployment; no current release or model-provider bill is inferred from this measurement.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="the-measured-architecture"&gt;
&lt;a href="#the-measured-architecture" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The measured architecture
&lt;/h2&gt;
&lt;p&gt;The AWS setup contained:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;one ECS Fargate ARM task with 1 vCPU and 2 GiB of memory&lt;/li&gt;
&lt;li&gt;two private subnets and one public NAT Gateway&lt;/li&gt;
&lt;li&gt;encrypted EFS for Hermes state&lt;/li&gt;
&lt;li&gt;Hermes images accumulated in ECR&lt;/li&gt;
&lt;li&gt;three Secrets Manager secrets for Slack, GitHub, and provider credentials&lt;/li&gt;
&lt;li&gt;an S3 knowledge/context bucket and CloudWatch Logs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Hermes used Slack Socket Mode, so it did not have a public inbound endpoint. The private subnets still needed a path to Slack and provider APIs. That path was the NAT Gateway. No public endpoint and no outbound-network cost are two different things.&lt;/p&gt;
&lt;h2 id="the-measured-usage-cost-about-1089"&gt;
&lt;a href="#the-measured-usage-cost-about-1089" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The measured usage cost: about $108.9
&lt;/h2&gt;
&lt;p&gt;The measurement period was July 4 through August 8, 2026. Hermes accumulated about 833 runtime hours. The table uses Cost Explorer &lt;code&gt;Usage&lt;/code&gt; line items before credits and rounds each amount to the nearest cent.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Component&lt;/th&gt;
&lt;th style="text-align: right"&gt;Usage&lt;/th&gt;
&lt;th style="text-align: right"&gt;Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NAT Gateway hours and processing&lt;/td&gt;
&lt;td style="text-align: right"&gt;833 hours, about 12.3 GB&lt;/td&gt;
&lt;td style="text-align: right"&gt;about $38.04&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECS Fargate ARM&lt;/td&gt;
&lt;td style="text-align: right"&gt;833 vCPU-hours, 1,664 GB-hours&lt;/td&gt;
&lt;td style="text-align: right"&gt;about $32.92&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EFS Elastic Throughput data access&lt;/td&gt;
&lt;td style="text-align: right"&gt;about 730.5 GB&lt;/td&gt;
&lt;td style="text-align: right"&gt;about $31.97&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EFS storage&lt;/td&gt;
&lt;td style="text-align: right"&gt;less than 1 GB on average&lt;/td&gt;
&lt;td style="text-align: right"&gt;about $0.14&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Public IPv4 for the NAT Gateway&lt;/td&gt;
&lt;td style="text-align: right"&gt;about 833 hours&lt;/td&gt;
&lt;td style="text-align: right"&gt;about $4.17&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secrets Manager&lt;/td&gt;
&lt;td style="text-align: right"&gt;three secrets&lt;/td&gt;
&lt;td style="text-align: right"&gt;about $1.32&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECR storage&lt;/td&gt;
&lt;td style="text-align: right"&gt;accumulated usage during the period&lt;/td&gt;
&lt;td style="text-align: right"&gt;about $0.34&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EFS backup&lt;/td&gt;
&lt;td style="text-align: right"&gt;less than 1 GB&lt;/td&gt;
&lt;td style="text-align: right"&gt;about $0.02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: right"&gt;&lt;/td&gt;
&lt;td style="text-align: right"&gt;&lt;strong&gt;about $108.9&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;S3 and CloudWatch Logs were effectively rounding errors at this workload size. The table excludes the LLM. Hermes used the OpenAI Codex provider, while AWS Bedrock usage was $0. Mixing an OpenAI subscription or another external provider bill into AWS infrastructure cost would make the comparison less useful.&lt;/p&gt;
&lt;h2 id="why-the-cash-bill-was-close-to-0"&gt;
&lt;a href="#why-the-cash-bill-was-close-to-0" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why the cash bill was close to $0
&lt;/h2&gt;
&lt;p&gt;My AWS account had promotional credits during this period. Account-wide Usage was about $113.22 and almost the same amount was covered by credits, so the cash charge was close to $0.&lt;/p&gt;
&lt;p&gt;That did not make the architecture free. A more accurate interpretation is that &lt;strong&gt;one personal agent consumed about $109 of AWS credits in a little over a month&lt;/strong&gt;. After the credits expire, the same resources become a normal bill. This is why I record pre-credit usage even when an account&amp;rsquo;s current invoice looks harmless.&lt;/p&gt;
&lt;h2 id="the-efs-throughput-surprise"&gt;
&lt;a href="#the-efs-throughput-surprise" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The EFS throughput surprise
&lt;/h2&gt;
&lt;p&gt;The file system held only about 924 MB at the final measurement, but EFS Elastic Throughput data access reached about 730 GB. Looking only at stored capacity would miss this difference.&lt;/p&gt;
&lt;p&gt;I did not trace every Hermes file operation, so I cannot honestly attribute the total to one SQLite or startup behavior. The bill does show that repeated I/O on a small file system can cost far more than storing the files themselves.&lt;/p&gt;
&lt;h2 id="nat-gateway-charges-even-with-low-traffic"&gt;
&lt;a href="#nat-gateway-charges-even-with-low-traffic" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
NAT Gateway charges even with low traffic
&lt;/h2&gt;
&lt;p&gt;Hermes had no public inbound endpoint. The private subnets still needed outbound access to Slack and provider APIs, so the NAT Gateway remained provisioned around the clock. Its hourly charge continued even when traffic was low.&lt;/p&gt;
&lt;p&gt;During this period, the fixed hourly charge was much larger than the data-processing charge. For a personal agent, that makes a public-subnet design with appropriate controls, a NAT instance, VPC endpoints, or an already-running home server worth evaluating. The right choice depends on security requirements and how much infrastructure you want to operate.&lt;/p&gt;
&lt;h2 id="what-i-would-estimate-first-next-time"&gt;
&lt;a href="#what-i-would-estimate-first-next-time" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What I would estimate first next time
&lt;/h2&gt;
&lt;p&gt;I would calculate these four items before writing the deployment plan:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;vCPU and memory hours for the always-on task&lt;/li&gt;
&lt;li&gt;fixed NAT Gateway and public IPv4 cost for private-subnet egress&lt;/li&gt;
&lt;li&gt;file-system throughput mode and data access, not just stored capacity&lt;/li&gt;
&lt;li&gt;the pre-credit price after promotional credits disappear&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Current rates are available from &lt;a href="https://aws.amazon.com/vpc/pricing/"&gt;AWS VPC pricing&lt;/a&gt;, &lt;a href="https://aws.amazon.com/fargate/pricing/"&gt;AWS Fargate pricing&lt;/a&gt;, &lt;a href="https://aws.amazon.com/efs/pricing/"&gt;Amazon EFS pricing&lt;/a&gt;, and &lt;a href="https://aws.amazon.com/secrets-manager/pricing/"&gt;AWS Secrets Manager pricing&lt;/a&gt;. These numbers are one deployment&amp;rsquo;s measured usage in one region, not a fixed price for every Hermes installation.&lt;/p&gt;
&lt;p&gt;After measuring this cost, I moved Hermes to Synology and documented the lossless process separately: &lt;a href="./blog/migrate-hermes-from-aws-to-synology/"&gt;Migrating Hermes from AWS ECS to Synology Without Losing Its Memory&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Migrating Hermes from AWS ECS to Synology Without Losing Its Memory</title><link>https://www.yopa.page/blog/2026-08-08-migrate-hermes-from-aws-to-synology.html</link><pubDate>Sat, 08 Aug 2026 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-08-migrate-hermes-from-aws-to-synology.html</guid><description>
&lt;p&gt;In an earlier post, I deployed Hermes Agent on ECS Fargate and kept its state on EFS. It worked well: the Slack bot stayed online, and replacing a container did not erase conversations or memory.&lt;/p&gt;
&lt;p&gt;The architecture was also larger than I needed for one personal agent. I was operating ECS, EFS, a NAT Gateway, ECR, Secrets Manager, S3, and logs. I already had a Synology NAS running around the clock, so I asked a simpler question:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Does this workload need Fargate, or does it need a recoverable Docker Compose deployment?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Moving the container was easy. The real work was moving SQLite state without losing a transaction and ensuring that AWS and Synology never acted as competing Slack consumers.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Review note (2026-08-28):&lt;/strong&gt; This is a completed migration snapshot, including the pinned &lt;code&gt;v2026.8.3&lt;/code&gt; image. Before repeating it, resolve the current release and digest from the &lt;a href="https://hermes-agent.nousresearch.com/docs/user-guide/docker/"&gt;Hermes Docker/configuration docs&lt;/a&gt;, then preserve the same stop, backup, and rollback gates; do not copy the historical tag blindly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="the-target-architecture"&gt;
&lt;a href="#the-target-architecture" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The target architecture
&lt;/h2&gt;
&lt;p&gt;The AWS deployment used one Fargate task, encrypted EFS storage, and Slack Socket Mode. It had no load balancer or public inbound endpoint.&lt;/p&gt;
&lt;p&gt;The Synology version is smaller:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Slack Cloud
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ⇅ outbound TLS WebSocket
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Synology NAS
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─ Docker bridge
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─ Hermes container
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─ /volume1/docker/hermes/data
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Hermes opens the outbound WebSocket to Slack. Slack does not initiate a connection to a public address on the NAS, so this setup needs no router port forwarding or published Hermes port. It is not a private link to Slack, though. Traffic still crosses the public Internet, and Slack remains a trusted external service.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://docs.slack.dev/apis/events-api/using-socket-mode/"&gt;Slack&amp;rsquo;s Socket Mode documentation&lt;/a&gt; describes the same boundary: Events API delivery without a public HTTP Request URL. Docker&amp;rsquo;s bridge network provides outbound masquerading, while inbound access depends on explicitly published ports. This Compose file publishes none.&lt;/p&gt;
&lt;h2 id="pin-the-runtime-before-moving-data"&gt;
&lt;a href="#pin-the-runtime-before-moving-data" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pin the runtime before moving data
&lt;/h2&gt;
&lt;p&gt;This migration also upgraded Hermes from &lt;code&gt;0.19.0&lt;/code&gt; to &lt;code&gt;0.20.0&lt;/code&gt;. Combining a host move with an application upgrade makes failures harder to classify, so I treated them as separate gates even though they happened in one maintenance window.&lt;/p&gt;
&lt;p&gt;The NAS uses the official image with both a release tag and a verified amd64 digest:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;hermes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;nousresearch/hermes-agent:v2026.8.3@sha256:&amp;lt;verified-amd64-digest&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unless-stopped&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&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="s2"&gt;&amp;#34;gateway&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;run&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;env_file&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;.env&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;HERMES_UID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;lt;measured-uid&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;HERMES_GID&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;lt;measured-gid&amp;gt;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/volume1/docker/hermes/data:/opt/data&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mem_limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;6g&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;stop_grace_period&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;30s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;I did not use &lt;code&gt;latest&lt;/code&gt;, and I did not override the official &lt;code&gt;/init&lt;/code&gt; entrypoint. That entrypoint fixes initial permissions and drops the Hermes process to the configured non-root identity.&lt;/p&gt;
&lt;p&gt;UID, GID, storage capacity, and the Dockge stacks directory were measured on the NAS. They are machine-specific values, not defaults another Synology should copy blindly.&lt;/p&gt;
&lt;h2 id="stop-aws-before-taking-the-final-backup"&gt;
&lt;a href="#stop-aws-before-taking-the-final-backup" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Stop AWS before taking the final backup
&lt;/h2&gt;
&lt;p&gt;My first plan was to create the final backup inside the live ECS task and immediately scale the service to zero. I changed that plan when I made the priority explicit: losing no data mattered more than avoiding downtime.&lt;/p&gt;
&lt;p&gt;The final sequence was:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Stop using the Slack bot.&lt;/li&gt;
&lt;li&gt;Set the ECS service desired count to zero.&lt;/li&gt;
&lt;li&gt;Verify that the running task count is zero.&lt;/li&gt;
&lt;li&gt;Start a one-off maintenance task that mounts the same EFS but does not run the gateway.&lt;/li&gt;
&lt;li&gt;Use that task only for inspection and backup.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The maintenance task reused the task definition, subnets, security group, and EFS mount, but overrode the container command with a long &lt;code&gt;sleep&lt;/code&gt;. ECS Exec remained available, while there was no Slack consumer and no SQLite writer.&lt;/p&gt;
&lt;p&gt;Once downtime was acceptable, this was much easier to reason about. There was no race between the last backup and a final incoming message.&lt;/p&gt;
&lt;h2 id="a-sqlite-database-is-more-than-statedb"&gt;
&lt;a href="#a-sqlite-database-is-more-than-statedb" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A SQLite database is more than &lt;code&gt;state.db&lt;/code&gt;
&lt;/h2&gt;
&lt;p&gt;Hermes stores sessions and messages in SQLite. The EFS directory contained:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;state.db
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;state.db-wal
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;state.db-shm
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In WAL mode, these files belong to one database generation. Copying only the main file can omit committed work. Placing an old WAL or SHM file beside a newly restored database can also corrupt the result.&lt;/p&gt;
&lt;p&gt;With the writer stopped, I used Python&amp;rsquo;s SQLite backup API:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;sqlite3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;file:/data/hermes/state.db?mode=ro&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sqlite3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/tmp/state.consistent.db&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;backup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;I archived the full state directory together with &lt;code&gt;state.consistent.db&lt;/code&gt;, uploaded it to a private S3 migration prefix, and downloaded it again to a mode-700 directory on my Mac. The round trip was checked with SHA-256, tar extraction, and a second database inspection.&lt;/p&gt;
&lt;p&gt;The useful invariants were:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;PRAGMA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;quick_check&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;PRAGMA&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;foreign_key_check&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;version&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;schema_version&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;sessions&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The final AWS generation had schema version 22, 19 sessions, and 479 messages. &lt;code&gt;quick_check&lt;/code&gt; returned &lt;code&gt;ok&lt;/code&gt;, and there were no foreign-key errors. Those values became the acceptance criteria for every later step.&lt;/p&gt;
&lt;h2 id="build-a-new-nas-generation"&gt;
&lt;a href="#build-a-new-nas-generation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Build a new NAS generation
&lt;/h2&gt;
&lt;p&gt;I preserved the rehearsal directory under a timestamped name instead of deleting it. A fresh data directory then received only the state Hermes needed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;SOUL.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;auth.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;the consistent &lt;code&gt;state.db&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;memories&lt;/li&gt;
&lt;li&gt;skills&lt;/li&gt;
&lt;li&gt;sessions&lt;/li&gt;
&lt;li&gt;Slack pairing state&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I did not reuse the AWS &lt;code&gt;config.yaml&lt;/code&gt;. A minimal v0.20 configuration carried over the provider, model, reasoning effort, &lt;code&gt;/opt/data&lt;/code&gt; path, and Slack platform. AWS-specific paths and the Bedrock fallback were removed.&lt;/p&gt;
&lt;p&gt;The first boot had no Slack tokens. Hermes migrated the database from schema 22 to 25 while preserving all 19 sessions and 479 messages. The integrity checks still passed.&lt;/p&gt;
&lt;p&gt;If this gate had failed, the final AWS archive and the NAS rehearsal directory would still have been untouched.&lt;/p&gt;
&lt;h2 id="two-dockge-failures-worth-remembering"&gt;
&lt;a href="#two-dockge-failures-worth-remembering" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Two Dockge failures worth remembering
&lt;/h2&gt;
&lt;p&gt;The first failure was a CPU limit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;NanoCPUs can not be set, as your kernel does not support CPU CFS scheduler
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;or the cgroup is not mounted
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The DS918+ kernel did not support Compose &lt;code&gt;cpus: 3.0&lt;/code&gt;. Removing &lt;code&gt;cpus&lt;/code&gt; allowed the container to start, while the supported memory limit remained. A Compose option being valid does not mean every Synology kernel implements its underlying cgroup feature.&lt;/p&gt;
&lt;p&gt;The second failure was subtler. I installed the Slack tokens in a mode-600 &lt;code&gt;.env&lt;/code&gt; and ran Dockge Update, but the gateway still had no platforms. Changing only &lt;code&gt;env_file&lt;/code&gt; content had not recreated the existing container, so it retained the old empty environment.&lt;/p&gt;
&lt;p&gt;I added a harmless generation marker to change the Compose configuration hash:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;HERMES_CUTOVER_GENERATION&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;20260808T182908Z&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Dockge Update then recreated the container. I also restored &lt;code&gt;platforms/pairing&lt;/code&gt; from the AWS archive so the previously approved Slack user remained approved.&lt;/p&gt;
&lt;p&gt;The practical lesson was simple:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“Running” in a UI does not prove that the process is using the new configuration.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I required five independent signals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a new container boot timestamp;&lt;/li&gt;
&lt;li&gt;a current gateway heartbeat;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;platforms.slack.state=connected&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;authentication and Socket Mode connection logs;&lt;/li&gt;
&lt;li&gt;a real DM response with SOUL and memory recall.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The final NAS database stayed at 19 sessions and 479 messages after migration, now on schema 25, and the real Slack DM worked.&lt;/p&gt;
&lt;h2 id="the-security-boundary-changed"&gt;
&lt;a href="#the-security-boundary-changed" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The security boundary changed
&lt;/h2&gt;
&lt;p&gt;No published ports is a good start, but it does not make the container harmless. A compromised container may have outbound Internet access and may be able to probe LAN resources reachable by the NAS. Its Hermes bind mount is read-write.&lt;/p&gt;
&lt;p&gt;I keep these controls around the smaller deployment:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DSM, Dockge, and SSH are reachable only from the LAN or a private VPN.&lt;/li&gt;
&lt;li&gt;There is no router port forwarding for Hermes, Dockge, or DSM.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.env&lt;/code&gt; is mode 600 and excluded from Git.&lt;/li&gt;
&lt;li&gt;Slack and GitHub tokens should have the smallest practical scope.&lt;/li&gt;
&lt;li&gt;The image tag and digest are pinned.&lt;/li&gt;
&lt;li&gt;Data, &lt;code&gt;.env&lt;/code&gt;, and OAuth state belong only in encrypted backups.&lt;/li&gt;
&lt;li&gt;An explicit Slack member allowlist is a follow-up hardening item.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Moving from managed AWS services to Compose did not remove operational responsibility. It moved that responsibility to my NAS.&lt;/p&gt;
&lt;h2 id="rollback-does-not-always-mean-start-aws"&gt;
&lt;a href="#rollback-does-not-always-mean-start-aws" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Rollback does not always mean “start AWS”
&lt;/h2&gt;
&lt;p&gt;Before the NAS accepts a real message, restarting the old AWS service is a valid rollback. After the NAS creates new sessions or messages, it is not. Starting the stale EFS generation would discard everything written after cutover.&lt;/p&gt;
&lt;p&gt;The post-cutover recovery sequence is therefore:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Stop the NAS gateway.&lt;/li&gt;
&lt;li&gt;Create a consistent backup of the newest NAS state.&lt;/li&gt;
&lt;li&gt;Repair the NAS, or restore that newer generation to EFS if AWS must return.&lt;/li&gt;
&lt;li&gt;Verify the database invariants before starting any gateway.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I do not merge two SQLite databases. I select one complete, newest generation as the authoritative copy.&lt;/p&gt;
&lt;h2 id="closing-thoughts"&gt;
&lt;a href="#closing-thoughts" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Closing thoughts
&lt;/h2&gt;
&lt;p&gt;Docker Compose was the easiest part of this migration. The real design was the order in which writers disappeared, database generations moved, invariants were checked, and rollback changed meaning.&lt;/p&gt;
&lt;p&gt;For a personal workload, several minutes of downtime can be cheaper than an ambiguous recovery path. Once I accepted that tradeoff, the migration became much safer.&lt;/p&gt;
&lt;p&gt;AWS was not deleted at cutover. EFS, the final archive, secrets, images, and infrastructure state remain until a seven-day soak, an off-box backup, and a real restore test are complete.&lt;/p&gt;
&lt;p&gt;Sources verified on &lt;strong&gt;2026-08-08&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="references"&gt;
&lt;a href="#references" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
References
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/NousResearch/hermes-agent/blob/main/website/docs/user-guide/docker.md"&gt;Hermes Docker guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://hermes-agent.nousresearch.com/docs/user-guide/messaging/slack/"&gt;Hermes Slack setup&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.slack.dev/apis/events-api/using-socket-mode/"&gt;Slack Socket Mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/engine/network/drivers/bridge/"&gt;Docker bridge networking&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.backup"&gt;Python sqlite3 backup API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>AgentCore Runtime Instances: When Your Agent Outgrows the MicroVM</title><link>https://www.yopa.page/blog/2026-08-06-agentcore-runtime-instances.html</link><pubDate>Thu, 06 Aug 2026 09:00:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-06-agentcore-runtime-instances.html</guid><description>
&lt;p&gt;On August 6, 2026 AWS made &lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/08/aws-bedrock-agentcore-runtime-instances-generally-available/"&gt;AgentCore runtime instances&lt;/a&gt; generally available. Until now AgentCore Runtime gave you one shape of compute: a serverless microVM, fast to start, capped at an eight-hour session. That covers most request-response agents. It does not cover an agent that needs a GPU, or one that has to stay alive for two days.&lt;/p&gt;
&lt;p&gt;Runtime instances add a second shape. You still deploy and invoke the agent the same way, but now it can run on a managed EC2 instance you selected, for a session up to 14 days.&lt;/p&gt;
&lt;p&gt;The instances run on AWS-managed EC2 infrastructure in your account through a capacity provider. That changes the security and encryption model from the serverless microVM path, so review the &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-instances.html"&gt;Runtime Instances documentation&lt;/a&gt; before treating the two modes as interchangeable.&lt;/p&gt;
&lt;p&gt;If you read my &lt;a href="./blog/2026-08-01-durable-long-running-jobs-with-agentcore.html"&gt;Durable Long-Running Jobs with AgentCore&lt;/a&gt;, that post argued the eight-hour ceiling is a hard boundary you design around. The ceiling just moved, and for a subset of workloads that changes the design.&lt;/p&gt;
&lt;h2 id="one-running-example"&gt;
&lt;a href="#one-running-example" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
One running example
&lt;/h2&gt;
&lt;p&gt;Take a document-intelligence agent. It ingests a few hundred contracts, runs a local vision-language model to extract clauses, and produces a structured report. Two properties break the default runtime:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;It wants a GPU for the local model. The serverless microVM has no GPU.&lt;/li&gt;
&lt;li&gt;A full batch takes closer to a day than an hour.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Carry this example through both approaches.&lt;/p&gt;
&lt;h2 id="where-it-fits"&gt;
&lt;a href="#where-it-fits" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Where it fits
&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Request -&amp;gt; AgentCore Runtime -&amp;gt; your agent code
|
+-----------+-----------+
| |
microVM (serverless) runtime instance (EC2)
fast start, &amp;lt;= 8h your instance type, &amp;lt;= 14d
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;AgentCore still owns provisioning, patching, scaling, and lifecycle. The choice is only &lt;em&gt;what the agent runs on&lt;/em&gt;, and you can run a mix: latency-sensitive agents on microVMs, the heavy batch agent on an instance.&lt;/p&gt;
&lt;h2 id="before-bending-the-microvm-to-fit"&gt;
&lt;a href="#before-bending-the-microvm-to-fit" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Before: bending the microVM to fit
&lt;/h2&gt;
&lt;p&gt;With only the serverless runtime, the document agent forces two workarounds.&lt;/p&gt;
&lt;p&gt;For the GPU, you leave AgentCore Runtime entirely. You stand up your own ECS or EKS service on GPU instances, wire the networking and IAM yourself, and now you own patching and scaling for that fleet.&lt;/p&gt;
&lt;p&gt;For the duration, you chop the day-long batch into eight-hour-safe chunks behind a durable orchestrator, the exact pattern from the long-running-jobs post:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Step Functions
-&amp;gt; SQS work item (batch of 20 contracts)
-&amp;gt; AgentCore Runtime (bounded, &amp;lt; 8h)
-&amp;gt; S3 artifact + DynamoDB checkpoint
-&amp;gt; loop until done
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;That orchestration is still good design for genuinely durable business processes with approvals and money movement. But here it exists only to dodge a compute limit, not because the business process is durable. That is the tell.&lt;/p&gt;
&lt;h2 id="after-pick-the-compute-attach-done"&gt;
&lt;a href="#after-pick-the-compute-attach-done" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
After: pick the compute, attach, done
&lt;/h2&gt;
&lt;p&gt;With runtime instances the two workarounds collapse into configuration. You create a &lt;strong&gt;capacity provider&lt;/strong&gt; that names the EC2 instance types your agent needs, GPU-accelerated in this case, and attach the agent to it.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;capacity provider:
instance types: [ g6.xlarge, g6.2xlarge ] # GPU family
-&amp;gt;
agent: document-intelligence
runtime: instance
session: up to 14 days
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The GPU is now first-class, no side ECS fleet. The day-long batch runs in a single session instead of a Step Functions chunking loop. Deploy and invoke are unchanged, so the calling code from the microVM era still works.&lt;/p&gt;
&lt;p&gt;You still pay for the compute AgentCore manages on top of the underlying EC2 cost, so an instance sitting idle is real money in a way an autoscaled-to-zero microVM is not.&lt;/p&gt;
&lt;h2 id="which-one"&gt;
&lt;a href="#which-one" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Which one
&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Reach for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Request-response, spiky, sub-8h&lt;/td&gt;
&lt;td&gt;microVM (serverless)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Fast cold start matters&lt;/td&gt;
&lt;td&gt;microVM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Needs GPU or memory/compute-optimized hardware&lt;/td&gt;
&lt;td&gt;runtime instance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Single session must exceed 8h (up to 14d)&lt;/td&gt;
&lt;td&gt;runtime instance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sustained, always-warm workload&lt;/td&gt;
&lt;td&gt;runtime instance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Genuinely durable process (approvals, days of waiting)&lt;/td&gt;
&lt;td&gt;still a durable orchestrator, not one long session&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;That last row matters. A 14-day session is not a durable workflow. If your process waits two days for a human to approve something, a single long-lived session is a fragile place to park that state. Persist it in a transactional store and let Step Functions own the wait. Runtime instances raise the ceiling for &lt;em&gt;continuous&lt;/em&gt; work; they do not turn one invocation into a saga.&lt;/p&gt;
&lt;h2 id="pitfalls"&gt;
&lt;a href="#pitfalls" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pitfalls
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Idle instances bill.&lt;/strong&gt; The serverless runtime scales to zero. A capacity provider holding instances for a bursty agent burns money between bursts. Match the runtime to the traffic shape, not to the peak.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;14 days is a maximum, not a target.&lt;/strong&gt; Reaching for a multi-day session is usually a sign the work should be checkpointed and made restart-safe anyway, the same as under the eight-hour cap.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Regional availability is partial.&lt;/strong&gt; At GA: US East (N. Virginia, Ohio), US West (Oregon), Asia Pacific (Mumbai, Singapore, Sydney, Tokyo), Europe (Frankfurt, Ireland). Confirm your region before you design around it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Instance selection is your call.&lt;/strong&gt; The capacity provider is only as right-sized as the instance families you list. Over-provisioning a GPU family for an agent that never touches the GPU is the classic waste.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-to-do"&gt;
&lt;a href="#what-to-do" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What to do
&lt;/h2&gt;
&lt;p&gt;Keep the default. Most agents are request-response and belong on the serverless microVM, where scale-to-zero and fast start are free wins. Reach for a runtime instance only when a specific agent hits a wall the microVM can&amp;rsquo;t clear: a GPU requirement, memory/compute-optimized hardware, or a session that genuinely runs longer than eight hours of continuous work. And when you catch yourself wanting a 14-day session to hold a business process open, that is still the signal to reach for a durable orchestrator, as covered in the &lt;a href="./blog/2026-08-01-durable-long-running-jobs-with-agentcore.html"&gt;long-running jobs post&lt;/a&gt; and the broader &lt;a href="./blog/2026-08-01-agentcore-service-map-and-production-boundaries.html"&gt;AgentCore service map&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>DynamoDB Now Does Vector Search: When It Replaces Your Vector DB (and When It Doesn't)</title><link>https://www.yopa.page/blog/2026-08-06-dynamodb-native-vector-search.html</link><pubDate>Thu, 06 Aug 2026 09:00:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-06-dynamodb-native-vector-search.html</guid><description>
&lt;blockquote&gt;
&lt;p&gt;DynamoDB now supports native vector search. You store embeddings as an attribute on your items and run similarity search directly on the table, so one table is both your operational datastore and your vector store. This is a real simplification for a specific shape of app, and a trap for others. Here is the running example, the pipeline it deletes, and where the line actually sits.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Review note (2026-08-28):&lt;/strong&gt; The current API contract is &lt;code&gt;SearchVectors&lt;/code&gt;, not &lt;code&gt;Query&lt;/code&gt;. The example and the tenant-isolation warning below follow the current &lt;a href="https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/VectorSearchWorkingWith.html"&gt;DynamoDB vector-index documentation&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If you read my &lt;a href="./blog/2025-05-25-aws-vector-databases-rag-applications-complete-architectural-decision-guide.html"&gt;AWS vector database decision guide&lt;/a&gt;, this is a new entry on that same cost/latency curve. The pitch is different from every other option there: it is not a better vector store, it is &lt;em&gt;no separate vector store at all&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="the-running-example"&gt;
&lt;a href="#the-running-example" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The running example
&lt;/h2&gt;
&lt;p&gt;Say you run a product catalog. Every product already lives in a DynamoDB table, keyed by &lt;code&gt;product_id&lt;/code&gt;, with &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;price&lt;/code&gt;, &lt;code&gt;category&lt;/code&gt;. Reads and writes are already fast and cheap. Now the product team wants &amp;ldquo;find similar products&amp;rdquo; and natural-language search (&amp;ldquo;a warm jacket for hiking in the rain&amp;rdquo;) that matches by meaning, not keywords.&lt;/p&gt;
&lt;p&gt;That semantic layer needs embeddings and nearest-neighbor search. The question is where those vectors live.&lt;/p&gt;
&lt;h2 id="before-a-second-database-and-a-pipeline-to-keep-it-honest"&gt;
&lt;a href="#before-a-second-database-and-a-pipeline-to-keep-it-honest" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Before: a second database and a pipeline to keep it honest
&lt;/h2&gt;
&lt;p&gt;The standard answer until now was a dedicated vector store next to DynamoDB. You generate an embedding for each product, write it to the vector store keyed back to &lt;code&gt;product_id&lt;/code&gt;, and at query time you search the vector store, get IDs, then round-trip to DynamoDB to fetch the actual product rows.&lt;/p&gt;
&lt;p&gt;The cost is not the vector store itself. It is the plumbing:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;DynamoDB (source of truth)
| DynamoDB Streams
v
Lambda (embed on change) --&amp;gt; Bedrock (Titan embeddings)
|
v
Vector store (OpenSearch / pgvector / etc.) &amp;lt;-- must stay in sync
query time:
query --embed--&amp;gt; vector store --&amp;gt; [product_id, ...] --&amp;gt; DynamoDB BatchGetItem --&amp;gt; rows
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Three durable problems come bundled with that diagram:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sync drift.&lt;/strong&gt; Every product write has to fan out to the vector store. Miss one (Lambda error, throttle, a backfill that half-finished) and search silently returns stale or missing results. You now own a reconciliation job.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Two round trips.&lt;/strong&gt; The vector store returns IDs, so you still hit DynamoDB to get the row the user actually wants to see. That is latency and a second failure surface on every search.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Two bills, two scaling stories.&lt;/strong&gt; You provision, patch, and pay for a second datastore whose only job is to mirror data you already have.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="after-the-vector-is-just-an-attribute"&gt;
&lt;a href="#after-the-vector-is-just-an-attribute" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
After: the vector is just an attribute
&lt;/h2&gt;
&lt;p&gt;With native vector search you add a vector index to the table you already have. The embedding becomes an attribute on the product item. There is no second store and no sync pipeline, because the vector lives on the same item as the row it describes.&lt;/p&gt;
&lt;p&gt;Create the index (you can also add one to an existing table with &lt;code&gt;UpdateTable&lt;/code&gt;, no re-create):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;dynamodb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_table&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;TableName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Products&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;AttributeDefinitions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;AttributeName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;product_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;AttributeType&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;S&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;KeySchema&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;AttributeName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;product_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;KeyType&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;HASH&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;VectorIndexes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;IndexName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;VectorIndex&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;VectorAttribute&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;AttributeName&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;embedding&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Dimensions&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# must match your embedding model&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;DistanceFunction&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;DOT_PRODUCT&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# COSINE | DOT_PRODUCT | EUCLIDEAN&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Projection&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ProjectionType&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ALL&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;BillingMode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;PAY_PER_REQUEST&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Write a product with its embedding on the same item, in the same call that writes the row:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;emb&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bedrock_embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;. &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Titan Text Embeddings V2, normalized&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;table&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Item&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;product_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;title&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;price&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;category&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;category&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;embedding&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;emb&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Search returns the product rows directly, not just IDs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bedrock_embed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;a warm jacket for hiking in the rain&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;dynamodb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search_vectors&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;TableName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Products&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;IndexName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;VectorIndex&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;SearchVector&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;N&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;TopK&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ProjectionExpression&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;product_id, title, price, category&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Each result has an Item and a Score; no second round trip is needed.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Item&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;SearchResults&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The whole &lt;code&gt;Streams -&amp;gt; Lambda -&amp;gt; vector store&lt;/code&gt; diagram, plus the reconciliation job, plus the &lt;code&gt;BatchGetItem&lt;/code&gt; round trip, is gone. Because &lt;code&gt;Projection&lt;/code&gt; is &lt;code&gt;ALL&lt;/code&gt;, the search returns every projected non-vector attribute in one call; the large vector attribute itself is omitted from results by default.&lt;/p&gt;
&lt;p&gt;A few knobs that matter:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Dimensions&lt;/strong&gt; must equal your model&amp;rsquo;s output (Titan V2 is 1024). Up to 4,096 is supported.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Capacity and result limits&lt;/strong&gt;: vector indexes require on-demand capacity (&lt;code&gt;PAY_PER_REQUEST&lt;/code&gt;), and &lt;code&gt;TopK&lt;/code&gt; is capped at 100 per request.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Distance function&lt;/strong&gt;: use &lt;code&gt;DOT_PRODUCT&lt;/code&gt; when your embeddings are already unit-normalized (it equals cosine but skips the normalization step). Use &lt;code&gt;COSINE&lt;/code&gt; for un-normalized vectors, &lt;code&gt;EUCLIDEAN&lt;/code&gt; when magnitude matters. For &lt;code&gt;COSINE&lt;/code&gt;/&lt;code&gt;EUCLIDEAN&lt;/code&gt; lower is more similar, for &lt;code&gt;DOT_PRODUCT&lt;/code&gt; higher is more similar.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Projection&lt;/strong&gt;: &lt;code&gt;ALL&lt;/code&gt; returns the whole row, &lt;code&gt;INCLUDE&lt;/code&gt; returns a chosen subset (smaller, cheaper index), &lt;code&gt;KEYS_ONLY&lt;/code&gt; sends you back to a round trip. Pick &lt;code&gt;INCLUDE&lt;/code&gt; when items are large.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Search is ANN&lt;/strong&gt; (approximate nearest neighbor), so you trade a sliver of exactness for predictable speed and cost. That is the right trade at scale, but it means recall is not 100 percent, which matters for the decision below.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="multi-tenant-isolation-the-partition-key-does-real-work-here"&gt;
&lt;a href="#multi-tenant-isolation-the-partition-key-does-real-work-here" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Multi-tenant isolation: the partition key does real work here
&lt;/h2&gt;
&lt;p&gt;If your catalog is multi-tenant (each customer sees only their own products), the vector index&amp;rsquo;s optional &lt;strong&gt;partition key&lt;/strong&gt; is the interesting part. Define the index with a partition key of &lt;code&gt;tenant_id&lt;/code&gt; and every search must supply a tenant value in &lt;code&gt;SearchConditionExpression&lt;/code&gt;, so DynamoDB searches only that tenant&amp;rsquo;s slice of the index. You get cheaper searches (less data scanned) and throughput that scales across partition-key values.&lt;/p&gt;
&lt;p&gt;This is useful data scoping, but it is &lt;strong&gt;not an access-control boundary&lt;/strong&gt;. Any principal with &lt;code&gt;dynamodb:SearchVectors&lt;/code&gt; on the index can submit another tenant&amp;rsquo;s partition-key value, and fine-grained &lt;code&gt;LeadingKeys&lt;/code&gt; conditions do not apply to this API. For strict tenant isolation, use separate tables or indexes with separate IAM grants; still enforce the tenant value in your application authorization path.&lt;/p&gt;
&lt;h2 id="so-when-does-this-actually-replace-your-vector-db"&gt;
&lt;a href="#so-when-does-this-actually-replace-your-vector-db" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
So when does this actually replace your vector DB?
&lt;/h2&gt;
&lt;p&gt;Reach for DynamoDB native vector when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DynamoDB is already your operational store&lt;/strong&gt; and the vectors describe rows you already keep there. This is the whole point. The win is deleting a pipeline, not gaining a better index.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Your dominant query is &amp;ldquo;give me the similar items and their data&amp;rdquo;&lt;/strong&gt; in one shot. Returning the row with the match is where this earns its keep.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You want per-tenant isolation and throughput&lt;/strong&gt; and a partition key maps cleanly onto your tenant boundary.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You want one bill and no second thing to operate.&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Do NOT reach for it when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;You need a full managed RAG pipeline.&lt;/strong&gt; DynamoDB gives you vector &lt;em&gt;search&lt;/em&gt;. It does not give you document chunking, an ingestion pipeline, or a retrieve-and-generate orchestration. If you want that turnkey, Bedrock Knowledge Bases is still the fast path, and you would be rebuilding its plumbing by hand.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Recall has to be near-exact&lt;/strong&gt; over a modest corpus. ANN plus a tuned dedicated index (OpenSearch) will beat a general-purpose store on recall quality. Measure on your own golden set before you commit, this is the one number that quietly decides the migration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Your source of truth is not in DynamoDB.&lt;/strong&gt; If the data lives in S3 or Postgres, adding DynamoDB just to hold vectors reintroduces the exact sync problem this feature removes, pointed the other way.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="pitfalls"&gt;
&lt;a href="#pitfalls" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pitfalls
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&amp;ldquo;Vector search&amp;rdquo; is not &amp;ldquo;RAG.&amp;rdquo;&lt;/strong&gt; The feature returns nearest items. Chunking, embedding orchestration, and generation are still yours to build. Do not scope a project as &amp;ldquo;we&amp;rsquo;ll use DynamoDB instead of a RAG stack&amp;rdquo; without accounting for the parts it does not do.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dimensions are fixed at index creation and must match the model.&lt;/strong&gt; Switching embedding models later (different dimension) means a new index and a backfill. Pin your model choice deliberately.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;KEYS_ONLY&lt;/code&gt; projection quietly brings back the round trip.&lt;/strong&gt; If you set it to keep the index small, you are back to a second &lt;code&gt;GetItem&lt;/code&gt; per hit. Use &lt;code&gt;INCLUDE&lt;/code&gt; with just the fields the result view needs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The read API is not &lt;code&gt;Query&lt;/code&gt; or &lt;code&gt;Scan&lt;/code&gt;.&lt;/strong&gt; Vector indexes are readable through &lt;code&gt;SearchVectors&lt;/code&gt; only. Results are capped at 16 MB with no pagination, so a wide &lt;code&gt;ALL&lt;/code&gt; projection plus a high &lt;code&gt;TopK&lt;/code&gt; can exceed the response limit; narrow the projection or reduce &lt;code&gt;TopK&lt;/code&gt; for large items.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Measure recall, not vibes.&lt;/strong&gt; ANN trades exactness for speed. A demo that looks great on ten queries can miss on the long tail. Build a golden set of query-to-expected-result pairs and check recall against your current store before migrating.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-to-actually-do"&gt;
&lt;a href="#what-to-actually-do" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What to actually do
&lt;/h2&gt;
&lt;p&gt;If you already run DynamoDB as your operational database and you have been eyeing a bolt-on vector store, prototype this first. Add a vector index to a copy of the table, embed a slice of your data, and run your real queries against it with a golden set to check recall. If recall holds, you delete an entire database, a sync pipeline, and a round trip. If it does not, you learned that cheaply, and the dedicated-store options in the &lt;a href="./blog/2025-05-25-aws-vector-databases-rag-applications-complete-architectural-decision-guide.html"&gt;decision guide&lt;/a&gt; are still there.&lt;/p&gt;
&lt;p&gt;The mental shift is the same one that post ended on: the question stopped being &amp;ldquo;which vector database&amp;rdquo; and became &amp;ldquo;do I need a separate one at all.&amp;rdquo;&lt;/p&gt;</description></item><item><title>Using AgentCore Identity Without a Shared Client Secret</title><link>https://www.yopa.page/blog/2026-07-30-agentcore-identity-private-key-jwt.html</link><pubDate>Thu, 30 Jul 2026 09:00:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-07-30-agentcore-identity-private-key-jwt.html</guid><description>
&lt;p&gt;Consider a customer-support agent that needs to read a customer&amp;rsquo;s order history from an internal orders API. The API is protected by an identity provider (IdP), so the agent needs an access token before it can make the request. To obtain that token, it first has to authenticate itself to the IdP.&lt;/p&gt;
&lt;p&gt;This authentication step is the focus of this post. In July 2026, AWS &lt;a href="https://aws.amazon.com/blogs/machine-learning/authenticate-with-private-key-jwt-using-amazon-bedrock-agentcore-identity/"&gt;added Private Key JWT client authentication to Amazon Bedrock AgentCore Identity&lt;/a&gt;, providing an alternative to shared client secrets. The &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/private-key-jwt.html"&gt;current AgentCore configuration docs&lt;/a&gt; are the source of truth for supported algorithms, key policy, and regional placement.&lt;/p&gt;
&lt;h2 id="where-identity-fits-in-agentcore"&gt;
&lt;a href="#where-identity-fits-in-agentcore" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Where Identity fits in AgentCore
&lt;/h2&gt;
&lt;p&gt;When an agent needs to call a protected downstream resource, it asks AgentCore Identity for a token:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;agent → GetResourceOauth2Token → (access token) → orders API
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;AgentCore Identity authenticates with the IdP&amp;rsquo;s token endpoint, obtains an access token, and returns it to the agent. The agent then uses the token to call the orders API.&lt;/p&gt;
&lt;p&gt;The important detail is how that authentication works. Before the IdP issues a token, the client, in this case AgentCore Identity acting for the agent, must prove its identity.&lt;/p&gt;
&lt;h2 id="before-the-shared-client-secret"&gt;
&lt;a href="#before-the-shared-client-secret" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Before: the shared client secret
&lt;/h2&gt;
&lt;p&gt;A traditional OAuth 2.0 client-credentials setup uses a shared secret. You register the agent as a client with the IdP, receive a &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt;, and store the secret. AgentCore Identity sends both values when it requests a token:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;POST /token
grant_type=client_credentials
client_id=support-agent
client_secret=SUPER_SECRET_VALUE ← the shared string
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The approach works, but it leaves you responsible for a long-lived credential. The secret must be stored somewhere, such as Secrets Manager, an environment variable, or a configuration store. Both parties retain a copy, and anyone who obtains it can impersonate the agent. That creates several operational problems:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Storage:&lt;/strong&gt; The secret has to be protected and audited wherever it is stored.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rotation:&lt;/strong&gt; The value must be changed at the IdP and in your own store without interrupting requests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exposure:&lt;/strong&gt; Anyone with the secret can request tokens as the agent, and the IdP cannot distinguish that request from a legitimate one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scale:&lt;/strong&gt; Each new agent or integration adds another secret to store and rotate.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The limitation comes from the symmetric trust model: both sides hold the same value, so possession of the secret is enough to authenticate.&lt;/p&gt;
&lt;h2 id="after-private-key-jwt"&gt;
&lt;a href="#after-private-key-jwt" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
After: Private Key JWT
&lt;/h2&gt;
&lt;p&gt;Private Key JWT replaces the symmetric secret with an asymmetric signature. You generate a key pair, register the &lt;strong&gt;public key&lt;/strong&gt; with the IdP, and keep the &lt;strong&gt;private key&lt;/strong&gt; in AWS KMS.&lt;/p&gt;
&lt;p&gt;The token request then works as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The agent calls &lt;code&gt;GetResourceOauth2Token&lt;/code&gt; on AgentCore Identity.&lt;/li&gt;
&lt;li&gt;AgentCore Identity reads the credential provider configuration (client ID, KMS key ARN, and signing algorithm), creates a short-lived JWT client assertion, and calls &lt;code&gt;kms:Sign&lt;/code&gt; with the KMS key.&lt;/li&gt;
&lt;li&gt;KMS signs the assertion and returns the signature. &lt;strong&gt;The private key remains in KMS.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;AgentCore Identity posts the signed assertion to your IdP&amp;rsquo;s token endpoint with &lt;code&gt;grant_type=client_credentials&lt;/code&gt; and &lt;code&gt;client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The IdP verifies the signature against the public key you registered and returns an access token.&lt;/li&gt;
&lt;li&gt;AgentCore Identity returns the token to the agent, which uses it to call the orders API.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The agent and downstream API do not change. Only the client-authentication step changes: instead of sending a shared secret, the client proves that it can use the private key without extracting or transmitting it.&lt;/p&gt;
&lt;p&gt;The difference is also visible in the credential provider configuration. With a shared secret, the sensitive value is part of the configuration:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;client_id: support-agent
client_secret: SUPER_SECRET_VALUE ← the thing you must guard
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;With Private Key JWT, the configuration points to a KMS key, and IAM controls who can use it. The KMS key must be in the same Region as the credential provider (cross-account use is supported when the key policy allows it):&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;client_id: support-agent
kms_key_arn: arn:aws:kms:us-east-1:111122223333:key/....
signing_alg: ES256 ← no private key in the config
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This addresses many of the operational problems of a shared secret:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Storage:&lt;/strong&gt; The private key stays in KMS; the configuration stores only its ARN.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rotation:&lt;/strong&gt; You can create a new key, register its public key, and then retire the old one without copying private key material.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Exposure:&lt;/strong&gt; A leaked key ARN is not sufficient to authenticate without &lt;code&gt;kms:Sign&lt;/code&gt; permission.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scale:&lt;/strong&gt; Each agent can have its own key, policy, and audit trail.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="choosing-a-grant-flow"&gt;
&lt;a href="#choosing-a-grant-flow" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Choosing a grant flow
&lt;/h2&gt;
&lt;p&gt;Private Key JWT authenticates the &lt;em&gt;client&lt;/em&gt;. It does not determine whose identity the resulting token represents. AgentCore Identity supports three grant flows, and the right choice depends on who the agent is acting as:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Machine-to-machine (M2M):&lt;/strong&gt; The agent acts as itself, without a user identity. It uses the &lt;code&gt;client_credentials&lt;/code&gt; grant, and the token subject is the client. The example in this post fits this flow because the order-history lookup is a service-level operation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;On-behalf-of (OBO):&lt;/strong&gt; The agent acts for a specific user who already has a token. AgentCore Identity exchanges the incoming user token for a downstream token (RFC 8693 token exchange or the RFC 7523 JWT authorization grant) while authenticating the client with its assertion.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;User-delegated access:&lt;/strong&gt; The agent acts for a user who does not yet have a token. The user completes an interactive sign-in and consent flow (a three-legged authorization-code flow) before the agent can act.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the support agent had to read data using the customer&amp;rsquo;s own identity and permissions, OBO would be a better fit. For a service-level lookup, M2M is appropriate. Private Key JWT can authenticate the client in all three flows.&lt;/p&gt;
&lt;h2 id="details-to-check"&gt;
&lt;a href="#details-to-check" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Details to check
&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;The signing algorithm must match across three systems.&lt;/strong&gt; The algorithm required by the IdP must be supported by AWS KMS and AgentCore Identity, and it must match the credential provider configuration. The available options are RS256, PS256, and ES256, with a compatible KMS key specification. The AWS example uses &lt;code&gt;ECC_NIST_P256&lt;/code&gt; with &lt;code&gt;ES256&lt;/code&gt;. Choose the algorithm first, then create a KMS key that supports it. A mismatch typically appears as a verification failure at the token endpoint rather than during key creation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Restrict the key with &lt;code&gt;kms:ViaService&lt;/code&gt;.&lt;/strong&gt; Grant &lt;code&gt;kms:Sign&lt;/code&gt; and &lt;code&gt;kms:DescribeKey&lt;/code&gt; in the KMS key policy as required by the provider, but constrain use with a &lt;code&gt;kms:ViaService&lt;/code&gt; condition for &lt;code&gt;bedrock-agentcore-identity.&amp;lt;region&amp;gt;.amazonaws.com&lt;/code&gt;. This limits signing to requests made through AgentCore Identity, even if another principal has a broad KMS permission.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Choose where the key pair is generated.&lt;/strong&gt; You can create the key pair in KMS and send the public key to the IdP (&lt;code&gt;kms:GetPublicKey&lt;/code&gt;), or generate it at the IdP and import the private material into KMS (&lt;code&gt;kms:GetParametersForImport&lt;/code&gt; and &lt;code&gt;kms:ImportKeyMaterial&lt;/code&gt;). Generating the key in KMS keeps the private key non-exportable from the start and is preferable unless the IdP requires the second approach.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Audit signing calls in CloudTrail.&lt;/strong&gt; Each &lt;code&gt;kms:Sign&lt;/code&gt; call made by the credential provider is recorded in CloudTrail. These events provide a record of when and under whose request the key was used. A shared secret reused outside your environment does not provide the same visibility.&lt;/p&gt;
&lt;h2 id="migration-guidance"&gt;
&lt;a href="#migration-guidance" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration guidance
&lt;/h2&gt;
&lt;p&gt;For an existing credential provider that uses a shared client secret, migration requires a KMS key, public-key registration at the IdP, and a credential provider configuration change. The main benefit is removing a long-lived shared secret from the authentication path.&lt;/p&gt;
&lt;p&gt;For new integrations, Private Key JWT is a sensible default when the IdP supports it. It lets the client authenticate with a signature while keeping the private key inside KMS, reducing both secret-management work and the risk of credential exposure.&lt;/p&gt;</description></item><item><title>Charters and Plumbing: Why AI Coding Guidelines Always Sound Generic</title><link>https://www.yopa.page/blog/2026-07-18-charter-and-plumbing.html</link><pubDate>Sat, 18 Jul 2026 09:05:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-07-18-charter-and-plumbing.html</guid><description>
&lt;p&gt;Every org is publishing AI coding guidelines now. They all look about the same. Quality over quantity. Keep PRs small enough that a human can actually review them. Don&amp;rsquo;t use agents as a replacement for human review. We are each responsible for every line we submit. We critically evaluate AI outputs.&lt;/p&gt;
&lt;p&gt;All correct. Nothing to argue with. And that&amp;rsquo;s the problem.&lt;/p&gt;
&lt;h2 id="correct-and-enforced-are-not-the-same-thing"&gt;
&lt;a href="#correct-and-enforced-are-not-the-same-thing" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Correct and enforced are not the same thing
&lt;/h2&gt;
&lt;p&gt;&amp;ldquo;We are responsible for every line&amp;rdquo; is a pledge, not an enforcement. &amp;ldquo;We critically evaluate AI outputs&amp;rdquo; is fine, but evaluate with what? How do you verify &amp;ldquo;we take pride in our craftsmanship&amp;rdquo;? You can&amp;rsquo;t. A sentence you can&amp;rsquo;t measure, however right, ends up as a framed poster on the wall.&lt;/p&gt;
&lt;p&gt;These documents usually are what they are: not a document that grants new capability, but one that prevents accidents. That&amp;rsquo;s not a knock. As a charter that aligns culture and blocks the worst outcomes, it does its job. The trouble starts when you mistake the charter for the plumbing.&lt;/p&gt;
&lt;h2 id="a-loop-can-hit-its-stopping-condition-and-still-be-wrong"&gt;
&lt;a href="#a-loop-can-hit-its-stopping-condition-and-still-be-wrong" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A loop can hit its stopping condition and still be wrong
&lt;/h2&gt;
&lt;p&gt;I saw that line in a public agent-stack builder&amp;rsquo;s guide. A loop can meet its stopping condition and still produce the wrong result. Every test can be green while not a single test actually passed. Two reviewers can both click approve, and that approval is an opinion, not evidence.&lt;/p&gt;
&lt;p&gt;So good systems don&amp;rsquo;t appeal to a person&amp;rsquo;s conscience. They write facts into the environment. The gate is a script, not a meeting. &amp;ldquo;Done&amp;rdquo; is defined as &amp;ldquo;a passing test row exists in the database,&amp;rdquo; not &amp;ldquo;a reviewer clicked green.&amp;rdquo; A verdict is an opinion; a row is evidence. Mix the two and the lower layer hands the upper layer a summary instead of a fact, and the upper layer closes on top of a lie.&lt;/p&gt;
&lt;p&gt;One more thing. Make models of the same lineage review each other and they share blind spots. Wherever the writer is wrong, a reviewer from the same family misses it in exactly the same way. Review should come from a different lineage when you can manage it. If the grader and the answer-writer run on the same brain, that isn&amp;rsquo;t grading.&lt;/p&gt;
&lt;h2 id="you-cant-save-cost-with-a-pledge-either"&gt;
&lt;a href="#you-cant-save-cost-with-a-pledge-either" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
You can&amp;rsquo;t save cost with a pledge either
&lt;/h2&gt;
&lt;p&gt;The one solid section in any of these guidelines is always the cost section. Route simple tool calls to a cheaper model. Turn on prompt caching. Keep your model tiers in one place instead of scattered per project. The most expensive model isn&amp;rsquo;t required for every task. These aren&amp;rsquo;t advice, they&amp;rsquo;re switches. Flip them and the bill drops.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s the difference from the rest of the virtues. Cost rules are verifiable. Whether the cache broke, whether the cheap model actually handled the tool call, you can read that off the logs. Only measured rules get followed. The rest are just hoped for.&lt;/p&gt;
&lt;p&gt;A caution here. You often see a blanket rule: never spawn subagents. That&amp;rsquo;s an overcorrection. The real problem isn&amp;rsquo;t subagents, it&amp;rsquo;s that a child inherits the parent&amp;rsquo;s effort level, so the whole fleet runs at max cost and burns the context window in one shot. The thing to control is effort and a budget cap, not &amp;ldquo;don&amp;rsquo;t.&amp;rdquo; Forbidding a tool and capping a tool&amp;rsquo;s cost are different moves.&lt;/p&gt;
&lt;h2 id="so-what-do-you-actually-do"&gt;
&lt;a href="#so-what-do-you-actually-do" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
So what do you actually do
&lt;/h2&gt;
&lt;p&gt;This isn&amp;rsquo;t a case against guidelines. You need a charter. It sets direction and blocks the worst outcomes. But after you read the whole thing, ask yourself one question per line: if I betray this sentence, what stops me? If nothing stops you, it isn&amp;rsquo;t a guideline, it&amp;rsquo;s a wish.&lt;/p&gt;
&lt;p&gt;One gate that refuses to pass when there&amp;rsquo;s no passing test, even with the light green, keeps a team more honest than ten virtues you can&amp;rsquo;t measure. Listing good values and making those values impossible to betray are completely different jobs. Most orgs stop at the first one.&lt;/p&gt;
&lt;p&gt;Everyone already has the charter. What&amp;rsquo;s always missing is the plumbing.&lt;/p&gt;</description></item><item><title>WAF Comes to AgentCore Gateway: Perimeter Security for the MCP Front Door</title><link>https://www.yopa.page/blog/2026-07-02-waf-for-agentcore-gateway.html</link><pubDate>Thu, 02 Jul 2026 09:05:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-07-02-waf-for-agentcore-gateway.html</guid><description>
&lt;p&gt;If you expose an agent to the outside world, the scary part isn&amp;rsquo;t the model. It&amp;rsquo;s the front door. The gateway that fans a single request out to a dozen MCP tools, a memory store, and downstream integrations is the highest-leverage thing an attacker can hit. One malformed or abusive request there touches everything behind it.&lt;/p&gt;
&lt;p&gt;In June 2026 AWS made &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-waf.html"&gt;AWS WAF generally available for Amazon Bedrock AgentCore Gateway&lt;/a&gt;. This is a small announcement with an outsized architectural implication: the perimeter for agentic workloads now sits at the gateway, where it belongs.&lt;/p&gt;
&lt;h2 id="why-the-gateway-is-the-right-control-point"&gt;
&lt;a href="#why-the-gateway-is-the-right-control-point" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why the gateway is the right control point
&lt;/h2&gt;
&lt;p&gt;AgentCore Gateway is the MCP front door. A caller hits the gateway, and the gateway routes to whatever tools, agents, and integrations sit behind it. That fan-out is exactly why it&amp;rsquo;s the correct place to enforce protection:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;One config, everything covered.&lt;/strong&gt; A single Gateway-level WAF association protects all downstream tools and integrations at once. You don&amp;rsquo;t bolt WAF onto each tool.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It&amp;rsquo;s where abuse concentrates.&lt;/strong&gt; Rate abuse, bot traffic, and known-bad payloads all funnel through the same entry point. Defend the funnel, not each cup.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It&amp;rsquo;s the trust boundary.&lt;/strong&gt; Everything upstream of the gateway is untrusted; everything downstream you built. That&amp;rsquo;s the definition of a perimeter.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Before this, securing that boundary meant custom infrastructure in front of the gateway. Now it&amp;rsquo;s a managed association.&lt;/p&gt;
&lt;h2 id="what-you-actually-get-to-turn-on"&gt;
&lt;a href="#what-you-actually-get-to-turn-on" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What you actually get to turn on
&lt;/h2&gt;
&lt;p&gt;The announcement lists concrete capabilities. Mapped to what they defend against:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Control&lt;/th&gt;
&lt;th&gt;What it stops&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;IP-based access controls&lt;/td&gt;
&lt;td&gt;Callers from where you don&amp;rsquo;t do business&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rate-based rules&lt;/td&gt;
&lt;td&gt;A single client hammering the gateway (cost + DoS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Managed Rule Group: Bot Control&lt;/td&gt;
&lt;td&gt;Automated scraping and abuse traffic&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Managed Rule Group: Known Bad Inputs&lt;/td&gt;
&lt;td&gt;Payloads matching published exploit signatures&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WAF protection packs&lt;/td&gt;
&lt;td&gt;A curated bundle applied consistently&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Available in every region where both WAF and AgentCore Gateway are supported, so unlike some launches this isn&amp;rsquo;t gated to a handful of regions.&lt;/p&gt;
&lt;p&gt;The association is regional: one regional web ACL protects a gateway, and the web ACL and gateway must be in the same Region. AWS documents &lt;code&gt;FAIL_CLOSE&lt;/code&gt; as the default WAF integration behavior; &lt;code&gt;FAIL_OPEN&lt;/code&gt; is an explicit availability tradeoff. Watch the &lt;code&gt;WafBlocks&lt;/code&gt;, &lt;code&gt;WafFailOpens&lt;/code&gt;, and &lt;code&gt;WafFailCloses&lt;/code&gt; metrics when you roll this out.&lt;/p&gt;
&lt;h2 id="the-decision-tree-which-rules-when"&gt;
&lt;a href="#the-decision-tree-which-rules-when" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The decision tree: which rules, when
&lt;/h2&gt;
&lt;p&gt;Turning everything on is not the goal. Each rule has a false-positive cost, and agent traffic is weirder than normal web traffic. Here&amp;rsquo;s how I&amp;rsquo;d sequence it.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Rate-based rules first, always.&lt;/strong&gt; The cheapest, highest-value control. An agent gateway with no rate limit is a cost incident waiting to happen, before you even get to security. Set a ceiling that&amp;rsquo;s generous for real clients and lethal for a runaway loop.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IP controls if your caller set is knowable.&lt;/strong&gt; Internal or partner-only gateway? Allowlist the org&amp;rsquo;s ranges. Public gateway? Skip the allowlist, it&amp;rsquo;ll just cause pages.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Known Bad Inputs, in count mode first.&lt;/strong&gt; Turn it on counting, not blocking, watch for a week. Agent payloads (big JSON, tool schemas, embedded code) can trip signatures written for web forms. Promote to block only after you&amp;rsquo;ve confirmed the false-positive rate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bot Control last, and carefully.&lt;/strong&gt; Legitimate agent-to-agent (A2A) traffic can look exactly like a bot, because it is one. If your gateway serves other agents, Bot Control can shoot your own callers. Scope it or leave it off.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The theme: the more an agent gateway is used by other machines, the more the &amp;ldquo;block bots&amp;rdquo; instinct works against you. Web WAF assumes humans are good and bots are bad. Agent WAF can&amp;rsquo;t.&lt;/p&gt;
&lt;h2 id="pitfalls"&gt;
&lt;a href="#pitfalls" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pitfalls
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;WAF is perimeter, not authorization.&lt;/strong&gt; It blocks abusive traffic shapes. It does not decide whether an authenticated caller is allowed to invoke a specific tool. You still need real authz at the gateway (scoped roles, OAuth, tool-level permissions). WAF in front, authz inside.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Count mode before block mode, every managed rule.&lt;/strong&gt; The single most common WAF self-inflicted outage is enabling a managed group in block mode and discovering it eats legitimate traffic. Agent payloads make this worse. Count, observe, then block.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Rate limits need a per-client key, not just per-IP.&lt;/strong&gt; Many agents call through NAT or a shared egress, so per-IP rate limiting can lump distinct tenants together. If you can key on an auth identity, do it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t let WAF become your only layer.&lt;/strong&gt; A gateway WAF is one ring. The downstream tools still need input validation and least privilege. Perimeter security that assumes the inside is safe is how one bypass becomes total.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-to-do"&gt;
&lt;a href="#what-to-do" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What to do
&lt;/h2&gt;
&lt;p&gt;If you run an AgentCore Gateway that anything outside your own account can reach, associate a WAF, and start with a rate-based rule today. That one control alone closes the most likely incident (a runaway or abusive client). Then walk the rest of the tree in count mode. And keep the framing straight: WAF secures the shape of traffic hitting the front door. It is not a substitute for deciding, inside the gateway, who&amp;rsquo;s allowed to open which door.&lt;/p&gt;</description></item><item><title>Where Do You Run Agent-Generated Code? Lambda MicroVMs and the Isolation Tradeoff</title><link>https://www.yopa.page/blog/2026-07-02-where-to-run-agent-generated-code.html</link><pubDate>Thu, 02 Jul 2026 09:00:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-07-02-where-to-run-agent-generated-code.html</guid><description>
&lt;p&gt;If you build agents, you eventually hit the question nobody wants: the model just wrote some code, and now something has to run it. Not your code. Code the LLM generated a second ago, possibly wrong, possibly hostile if a prompt injection got through. Where does that run?&lt;/p&gt;
&lt;p&gt;In June 2026 AWS introduced &lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/06/aws-lambda-microvms/"&gt;Lambda MicroVMs&lt;/a&gt;, a compute primitive built for exactly this. The &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/microvms-how-it-works.html"&gt;Lambda MicroVM documentation&lt;/a&gt; is the source of truth for limits and regional availability; those details are moving quickly.&lt;/p&gt;
&lt;h2 id="the-three-things-you-want-and-cant-all-have"&gt;
&lt;a href="#the-three-things-you-want-and-cant-all-have" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The three things you want and can&amp;rsquo;t all have
&lt;/h2&gt;
&lt;p&gt;When you run untrusted code, you want three properties at once:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Isolation&lt;/strong&gt; so a bad process can&amp;rsquo;t read another tenant&amp;rsquo;s data or escape into your infra.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Speed&lt;/strong&gt; so the agent doesn&amp;rsquo;t stall for seconds every time it runs a snippet.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State&lt;/strong&gt; so a long task can pause, keep its working directory and memory, and resume later.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Historically you picked two. A shared container is fast and stateful but the isolation is a namespace, not a wall. A fresh Firecracker microVM per request is isolated and fast to boot but throws away all state. A full EC2 VM is isolated and stateful but nowhere near instant.&lt;/p&gt;
&lt;p&gt;Lambda MicroVMs are AWS&amp;rsquo;s claim that you can now have all three. It&amp;rsquo;s the same Firecracker tech underneath Lambda (the announcement cites 15+ trillion invocations a month), but exposed as a primitive you launch directly.&lt;/p&gt;
&lt;h2 id="what-it-actually-gives-you"&gt;
&lt;a href="#what-it-actually-gives-you" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What it actually gives you
&lt;/h2&gt;
&lt;p&gt;The concrete capabilities, stripped of marketing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;VM-level isolation&lt;/strong&gt; for multi-tenant execution, without you managing any virtualization layer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Near-instant launch&lt;/strong&gt;, plus suspend and resume for &lt;strong&gt;up to 8 hours&lt;/strong&gt;. This is the interesting part. A microVM can hold a paused task, and you resume it later instead of rebuilding context.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Images from a Dockerfile&lt;/strong&gt;, launched with a dedicated &lt;strong&gt;HTTPS URL&lt;/strong&gt; that speaks HTTP/2, gRPC, and WebSockets.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pay for baseline while running&lt;/strong&gt;, plus whatever you consume above baseline.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The suspend/resume window is what separates this from &amp;ldquo;just a fast sandbox.&amp;rdquo; An agent that runs a multi-step task, waits on a human approval, then continues, can now sit in a suspended microVM instead of holding a container open or serializing its whole state to disk and rehydrating.&lt;/p&gt;
&lt;h2 id="the-decision-do-you-actually-need-this"&gt;
&lt;a href="#the-decision-do-you-actually-need-this" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The decision: do you actually need this?
&lt;/h2&gt;
&lt;p&gt;This is the part that matters. Reaching for microVM isolation when a lighter option would do is over-engineering. Here&amp;rsquo;s how I&amp;rsquo;d decide.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;What to reach for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Agent runs short, pure-compute snippets you generated and control&lt;/td&gt;
&lt;td&gt;A managed Code Interpreter sandbox. Don&amp;rsquo;t build infra.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Untrusted / model-generated code, multi-tenant, needs network or filesystem&lt;/td&gt;
&lt;td&gt;Lambda MicroVMs. This is the case they built it for.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long task that pauses on human approval then resumes&lt;/td&gt;
&lt;td&gt;Lambda MicroVMs, for the suspend/resume window.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trusted code, your own logic, just needs to scale&lt;/td&gt;
&lt;td&gt;Plain Lambda or a container. Isolation is already fine.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The signal that you need real VM isolation is not &amp;ldquo;I&amp;rsquo;m running code.&amp;rdquo; It&amp;rsquo;s &amp;ldquo;I&amp;rsquo;m running code I did not write, on behalf of tenants who don&amp;rsquo;t trust each other.&amp;rdquo; If that sentence isn&amp;rsquo;t true, a managed sandbox is cheaper and simpler.&lt;/p&gt;
&lt;h2 id="pitfalls-id-watch-for"&gt;
&lt;a href="#pitfalls-id-watch-for" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pitfalls I&amp;rsquo;d watch for
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Isolation is not a license to skip input handling.&lt;/strong&gt; VM-level isolation stops an escape. It does not stop the agent from cheerfully running &lt;code&gt;rm -rf&lt;/code&gt; on the data volume you mounted, or exfiltrating a secret you passed in as an env var. Isolation contains blast radius; it doesn&amp;rsquo;t decide what&amp;rsquo;s inside the blast.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Suspension is not the same as running.&lt;/strong&gt; A suspended microVM preserves memory and disk and incurs snapshot-storage charges; baseline compute is charged while it is running. Terminate work you no longer need, and track suspended snapshots as well as active VMs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Regional availability has already changed.&lt;/strong&gt; The initial five regions expanded to ten on August 19, 2026: N. Virginia, Ohio, Oregon, Tokyo, Ireland, Mumbai, Singapore, Sydney, Frankfurt, and Stockholm. Check the &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/microvms-launching.html"&gt;current regional capability&lt;/a&gt; before designing around a region.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTPS-URL-per-microVM changes your network model.&lt;/strong&gt; Each VM gets its own endpoint. That&amp;rsquo;s convenient, but it&amp;rsquo;s also a lot of ephemeral surface. Front it, log it, and don&amp;rsquo;t let those URLs leak into agent-visible context where a prompt injection could target them.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-to-do"&gt;
&lt;a href="#what-to-do" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What to do
&lt;/h2&gt;
&lt;p&gt;If you run agents that execute code they generated, write down which of the four rows above you&amp;rsquo;re actually in. Most teams are in row 1 or row 4 and don&amp;rsquo;t need a microVM. The teams that genuinely need it, multi-tenant execution of untrusted code with pause/resume, now have a primitive built for the job instead of a pile of Firecracker glue. Pick based on the isolation you actually need, not the isolation that sounds safest.&lt;/p&gt;</description></item><item><title>When Google Standardizes the Thing You Built Anyway</title><link>https://www.yopa.page/blog/2026-06-22-okf-llm-wiki-as-a-standard.html</link><pubDate>Mon, 22 Jun 2026 18:30:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-06-22-okf-llm-wiki-as-a-standard.html</guid><description>
&lt;p&gt;For the last several months I&amp;rsquo;ve been building knowledge-base agents that do something slightly heretical. Instead of standing up a vector store, chunking documents, and running similarity search at query time, the agent reads source material once and compiles it into a wiki. A directory of markdown files with YAML frontmatter, cross-linked, with an index and a change log. When a downstream agent needs to answer a question, it reads the wiki the way a person would: open the index, follow the links, read the pages.&lt;/p&gt;
&lt;p&gt;It worked well. Better than the RAG pipeline it replaced, for the kind of slow-changing structured knowledge I was dealing with. But it always nagged at me. Vector RAG is what everyone writes blog posts about. It&amp;rsquo;s what the frameworks assume. My markdown-wiki thing felt like a toy I&amp;rsquo;d built in a corner, a clever hack that no serious team would adopt. I kept waiting for someone to tell me I was doing it wrong.&lt;/p&gt;
&lt;p&gt;Then Google published the Open Knowledge Format. The initial v0.1 article is not the end of the story: Google published &lt;a href="https://cloud.google.com/blog/products/data-analytics/okf-v0-2-adds-trust-signals/"&gt;OKF v0.2&lt;/a&gt; on July 24, 2026 with additive trust and provenance signals.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Review note (2026-08-28):&lt;/strong&gt; The examples below describe the v0.1 shape. In v0.2, &lt;code&gt;type&lt;/code&gt; remains the only required field, while optional &lt;code&gt;generated&lt;/code&gt;, &lt;code&gt;verified&lt;/code&gt;, &lt;code&gt;sources&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt;, &lt;code&gt;stale_after&lt;/code&gt;, and attestation vocabulary lets consumers judge provenance, freshness, lifecycle, and computation. &lt;code&gt;timestamp&lt;/code&gt; is superseded by &lt;code&gt;generated.at&lt;/code&gt;, and the body citation list is superseded by &lt;code&gt;sources&lt;/code&gt;; v0.1 bundles remain valid.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="what-okf-actually-is"&gt;
&lt;a href="#what-okf-actually-is" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What OKF actually is
&lt;/h2&gt;
&lt;p&gt;OKF is an open, versioned specification from the Google Cloud knowledge-catalog project. The v0.1 baseline is a knowledge corpus represented as a directory of markdown files with YAML frontmatter, and that&amp;rsquo;s it. No schema registry. No central authority. No required SDK. The v0.2 additions are optional vocabulary, not a mandatory runtime or service. The spec&amp;rsquo;s own line is &amp;ldquo;if you can &lt;code&gt;cat&lt;/code&gt; a file, you can read OKF; if you can &lt;code&gt;git clone&lt;/code&gt; a repo, you can ship it.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The structure is almost embarrassingly small:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Every concept is one markdown file. The file path minus &lt;code&gt;.md&lt;/code&gt; is its ID.&lt;/li&gt;
&lt;li&gt;Each file has a frontmatter block. Exactly one field is required: &lt;code&gt;type&lt;/code&gt;. Everything else (&lt;code&gt;title&lt;/code&gt;, &lt;code&gt;description&lt;/code&gt;, &lt;code&gt;resource&lt;/code&gt;, &lt;code&gt;tags&lt;/code&gt;, and the v0.1 &lt;code&gt;timestamp&lt;/code&gt; field) is recommended but optional; v0.2 uses &lt;code&gt;generated.at&lt;/code&gt; for that timestamp.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;index.md&lt;/code&gt; is a reserved filename for a directory listing. &lt;code&gt;log.md&lt;/code&gt; is reserved for a change history. Both are optional.&lt;/li&gt;
&lt;li&gt;Links between concepts are plain markdown links. A link from A to B just asserts &amp;ldquo;these are related.&amp;rdquo; The kind of relationship is left to the surrounding prose. Consumers treat every link as an untyped directed edge.&lt;/li&gt;
&lt;li&gt;Broken links are allowed. The target might be knowledge that hasn&amp;rsquo;t been written yet.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;That&amp;rsquo;s the v0.1 baseline. The conformance rules remain permissive in v0.2: a bundle is conformant if its non-reserved markdown files have parseable frontmatter and every frontmatter has a non-empty &lt;code&gt;type&lt;/code&gt;; the new trust fields are opt-in. Consumers are explicitly forbidden from rejecting a bundle over missing optional fields, unknown types, unknown keys, broken links, or a missing index. The format is built to stay useful while it&amp;rsquo;s half-written and partially machine-generated, which is exactly the state most real knowledge ends up in.&lt;/p&gt;
&lt;p&gt;And the spec names its inspirations directly. Section 10 lists &amp;ldquo;LLM wiki repos&amp;rdquo; and &amp;ldquo;metadata as code&amp;rdquo; as the patterns OKF is intentionally close to. That was the sentence that made me sit up. The thing I&amp;rsquo;d been quietly building was, word for word, one of the named reference patterns for an emerging open standard.&lt;/p&gt;
&lt;h2 id="why-a-directory-of-files-beats-a-vector-store-for-some-jobs"&gt;
&lt;a href="#why-a-directory-of-files-beats-a-vector-store-for-some-jobs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why a directory of files beats a vector store for some jobs
&lt;/h2&gt;
&lt;p&gt;The instinct in 2026 is that RAG means embeddings and a vector database. For a lot of problems that&amp;rsquo;s correct. If your corpus is huge, unstructured, and you need fuzzy recall over millions of chunks, you want a vector store.&lt;/p&gt;
&lt;p&gt;But vector RAG rediscovers your knowledge from scratch on every single query. It pulls the top-k chunks that are similar to the question and hands them to the model, and the model has to re-synthesize an answer, re-resolve contradictions, and re-trace relationships every time. The knowledge never compounds. The retrieval is only as good as the chunking, and the chunking throws away the structure that made the source coherent in the first place.&lt;/p&gt;
&lt;p&gt;A compiled wiki inverts that. You pay the synthesis cost once, at write time. Cross-references are already there. Contradictions have already been flagged and reconciled by whoever (or whatever) wrote the page. The relationships between concepts are encoded in the link graph, not reconstructed from cosine similarity. When the consuming agent reads a page, it gets the curated view, not a bag of nearest-neighbor fragments.&lt;/p&gt;
&lt;p&gt;So the decision is less &amp;ldquo;RAG vs not RAG&amp;rdquo; and more about the shape of your knowledge:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Slow-changing, structured, relationship-heavy knowledge&lt;/strong&gt; (a data model, a set of internal systems, a domain ontology): a compiled wiki wins. The structure is the value, and the wiki preserves it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Huge, fast-changing, unstructured corpora&lt;/strong&gt; (a support ticket history, a sprawling document hoard): vector RAG wins. You can&amp;rsquo;t hand-compile a million tickets, and you don&amp;rsquo;t need the relationships.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The honest answer for a lot of internal tooling is &amp;ldquo;both&amp;rdquo;&lt;/strong&gt;: a small curated wiki for the structured core, vector search over the long tail.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The wiki approach also has a property that matters more than it sounds: a human can read it. It&amp;rsquo;s just markdown in git. You get diffs, attribution, code review, and the ability to open it in Obsidian and look at the graph. A vector store is an opaque blob of floats. When the agent gives a weird answer, you can open the wiki page and see exactly what it read.&lt;/p&gt;
&lt;h2 id="the-conformance-cost-was-basically-zero"&gt;
&lt;a href="#the-conformance-cost-was-basically-zero" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The conformance cost was basically zero
&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s the part that turned the nagging feeling into something useful. I checked my existing wikis against the OKF conformance rules to see how far off I was.&lt;/p&gt;
&lt;p&gt;I was off by about two lines.&lt;/p&gt;
&lt;p&gt;My wiki generator was already emitting markdown files with YAML frontmatter, organized into a directory tree, with cross-links and a machine-readable index. The only thing the spec strictly requires that I wasn&amp;rsquo;t doing was the &lt;code&gt;type&lt;/code&gt; field in frontmatter. Adding it is one line in the generator&amp;rsquo;s frontmatter dict. The other gap was cosmetic: I had a machine-readable &lt;code&gt;index.json&lt;/code&gt; but not the spec&amp;rsquo;s &lt;code&gt;index.md&lt;/code&gt;, so I generate both now. The query library that consumes the index never noticed, because &lt;code&gt;index.md&lt;/code&gt; and &lt;code&gt;type&lt;/code&gt; are additive surface for external consumers, not changes to my runtime.&lt;/p&gt;
&lt;p&gt;Everything else lined up on its own. My cross-links were Obsidian-style &lt;code&gt;[[wikilinks]]&lt;/code&gt; rather than the spec&amp;rsquo;s &lt;code&gt;[text](/path.md)&lt;/code&gt;, but OKF treats links as soft guidance and never rejects a bundle over link format, so I kept the wikilinks (my reader parses them) and the bundle is still conformant. Untyped edges, permissive consumption, frontmatter-per-document: I&amp;rsquo;d arrived at the same design decisions independently, because they&amp;rsquo;re the decisions the problem pushes you toward.&lt;/p&gt;
&lt;h2 id="pitfalls-ive-actually-hit"&gt;
&lt;a href="#pitfalls-ive-actually-hit" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pitfalls I&amp;rsquo;ve actually hit
&lt;/h2&gt;
&lt;p&gt;Running this pattern for real surfaced a few things the spec doesn&amp;rsquo;t warn you about:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Duplicate pages are the default failure mode.&lt;/strong&gt; If the agent doesn&amp;rsquo;t check the index before writing, it creates a second page for an entity that already exists under a slightly different name. The fix is a hard rule: orient first, search the index, then write. The wiki degrades into a pile the moment you skip this.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Orphan pages are invisible.&lt;/strong&gt; A page with no inbound links might as well not exist, because nothing will ever traverse to it. Every new page needs at least a couple of inbound links from existing pages, or it&amp;rsquo;s dead weight.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The index and the log are the navigational backbone, not bookkeeping.&lt;/strong&gt; It&amp;rsquo;s tempting to skip updating them. Don&amp;rsquo;t. The index is how everything gets found; a stale index means real pages become unreachable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Broken links are fine until they&amp;rsquo;re not.&lt;/strong&gt; The spec is right that a broken link can mean &amp;ldquo;not written yet.&amp;rdquo; But you still want a lint pass that distinguishes intentional forward-references from typos, or the graph slowly rots.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-i-actually-got-out-of-this"&gt;
&lt;a href="#what-i-actually-got-out-of-this" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What I actually got out of this
&lt;/h2&gt;
&lt;p&gt;The practical win is small and concrete: a couple of lines in a generator and I can describe my knowledge bases as OKF-conformant instead of as a bespoke hack. When I explain the design to someone now, I have a reference to point at.&lt;/p&gt;
&lt;p&gt;The bigger thing is the feeling going away. I&amp;rsquo;d been carrying this low-grade doubt that the markdown-wiki approach was a toy, that I&amp;rsquo;d taken a wrong turn while everyone else went vector. It turns out the intuition was pointing the same direction Google is now standardizing, including the trust signals an agent-written corpus needs. The pattern wasn&amp;rsquo;t a hack. It was an early implementation of something that was going to get a spec eventually.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s worth writing down, if only as a note to self: when a simple design keeps working and you can&amp;rsquo;t find the standard it conforms to, sometimes the standard just hasn&amp;rsquo;t been written yet. Build the simple thing, but track the spec version and add trust signals before agents write at scale.&lt;/p&gt;</description></item><item><title>Owning the Learning Loop</title><link>https://www.yopa.page/blog/2026-06-15-owning-the-learning-loop.html</link><pubDate>Mon, 15 Jun 2026 14:14:50 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-06-15-owning-the-learning-loop.html</guid><description>
&lt;p&gt;There&amp;rsquo;s a line in Satya Nadella&amp;rsquo;s recent essay that I haven&amp;rsquo;t been able to put down:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;You can offload a task, or even a job, but you can never offload your learning.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;He frames the future of the firm around two kinds of capital. &lt;strong&gt;Human capital&lt;/strong&gt; is the knowledge, judgment, and pattern recognition of your people. &lt;strong&gt;Token capital&lt;/strong&gt; is the AI capability you build and &lt;em&gt;own&lt;/em&gt;. His argument is that human capital doesn&amp;rsquo;t shrink as token capital grows. It gets more valuable, because without human direction you just have &amp;ldquo;compute running in circles.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;I want to take that one rung down the ladder, from the firm to the individual, because I&amp;rsquo;ve been living a small version of it for months and the framing finally gave me the right words.&lt;/p&gt;
&lt;h2 id="the-model-is-rented-the-learning-is-bought"&gt;
&lt;a href="#the-model-is-rented-the-learning-is-bought" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The model is rented. The learning is bought.
&lt;/h2&gt;
&lt;p&gt;Here is the uncomfortable part of building on top of someone else&amp;rsquo;s frontier model: the model is not yours. It can be deprecated, repriced, rate-limited, or quietly swapped for a worse checkpoint on a Tuesday. Every capability you depend on inside that model is borrowed.&lt;/p&gt;
&lt;p&gt;So if the model is rented, what do you actually own?&lt;/p&gt;
&lt;p&gt;You own whatever you accumulate &lt;em&gt;around&lt;/em&gt; the model. The corrections you fed it. The evals that encode what &amp;ldquo;good&amp;rdquo; means for your work specifically. The traces of every task it got right and every task it botched. The knowledge base that turns six months of context into something queryable instead of something you re-explain every morning.&lt;/p&gt;
&lt;p&gt;That accumulation is the learning loop. And unlike the model, it&amp;rsquo;s yours. Nadella calls it the firm&amp;rsquo;s new IP and describes it as a hill-climbing machine that compounds. At the scale of one person, it&amp;rsquo;s the difference between an assistant that resets to zero every session and one that actually becomes a veteran of &lt;em&gt;your&lt;/em&gt; work.&lt;/p&gt;
&lt;h2 id="what-owning-the-loop-actually-looks-like"&gt;
&lt;a href="#what-owning-the-loop-actually-looks-like" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What owning the loop actually looks like
&lt;/h2&gt;
&lt;p&gt;The temptation is to treat &amp;ldquo;owning your AI&amp;rdquo; as picking the best model. That&amp;rsquo;s the wrong layer. The model is the commodity. The loop is the asset. Concretely, the loop is three things sitting on top of whatever model you happen to be renting this quarter:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Private evals.&lt;/strong&gt; Not MMLU, not someone&amp;rsquo;s leaderboard. Evals that measure whether the thing is getting better at the outcomes &lt;em&gt;you&lt;/em&gt; care about. For me that&amp;rsquo;s &amp;ldquo;did it correctly reconstruct the state of a project I haven&amp;rsquo;t touched in two weeks,&amp;rdquo; not &amp;ldquo;did it score well on a public benchmark.&amp;rdquo; If you can&amp;rsquo;t articulate your own definition of good, you&amp;rsquo;re outsourcing your standards to whoever made the benchmark.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Real traces from real use.&lt;/strong&gt; Every session is a training signal if you keep it. The tasks the agent fumbled are worth more than the ones it nailed, because they&amp;rsquo;re where the loop has room to climb. The point isn&amp;rsquo;t to fine-tune a model tomorrow. It&amp;rsquo;s that the raw material for improvement is being captured instead of evaporating.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A queryable institutional memory.&lt;/strong&gt; This is the one most people skip, and it&amp;rsquo;s the one that compounds hardest. A knowledge base, even a directory of plain Markdown notes, turns &amp;ldquo;what did we decide about X three months ago&amp;rdquo; from a re-derivation into a lookup. Institutional memory you can query is the difference between a tool and a colleague.&lt;/p&gt;
&lt;p&gt;None of this requires owning a model. It requires owning the &lt;em&gt;context&lt;/em&gt; around the model, and keeping it persistent.&lt;/p&gt;
&lt;h2 id="the-real-test-swap-the-model-keep-the-veteran"&gt;
&lt;a href="#the-real-test-swap-the-model-keep-the-veteran" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The real test: swap the model, keep the veteran
&lt;/h2&gt;
&lt;p&gt;Nadella has a sharp way of stating the test:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A company should be able to switch out a &amp;ldquo;generalist&amp;rdquo; model without losing the &amp;ldquo;company veteran&amp;rdquo; expertise built into their learning system.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That&amp;rsquo;s the whole thing in one sentence. Sovereignty isn&amp;rsquo;t &amp;ldquo;I run my own model.&amp;rdquo; Sovereignty is &amp;ldquo;I can throw out the model I&amp;rsquo;m using and the veteran survives.&amp;rdquo; If swapping models nukes everything your system knows about your work, you never owned anything. You were just a tenant in someone else&amp;rsquo;s capability.&lt;/p&gt;
&lt;p&gt;This reframes the commoditization fear, too. The anxiety that &amp;ldquo;AI is going to absorb my expertise and sell it back to everyone&amp;rdquo; is real, but it&amp;rsquo;s aimed at the wrong target. What gets commoditized is &lt;em&gt;static&lt;/em&gt; knowledge, the stuff that&amp;rsquo;s already in the training set. What doesn&amp;rsquo;t commoditize is a &lt;em&gt;compounding&lt;/em&gt; loop fed by traces nobody else has: your decisions, your corrections, your context. The defense against being commoditized isn&amp;rsquo;t to out-know the model. It&amp;rsquo;s to own a loop the model can&amp;rsquo;t see.&lt;/p&gt;
&lt;h2 id="pitfalls-ive-actually-hit"&gt;
&lt;a href="#pitfalls-ive-actually-hit" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pitfalls I&amp;rsquo;ve actually hit
&lt;/h2&gt;
&lt;p&gt;This is where the philosophy meets the floor. Things I learned by getting them wrong:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Persistent beats volatile, every time.&lt;/strong&gt; An agent that&amp;rsquo;s brilliant for one session and amnesiac the next is not a veteran. It&amp;rsquo;s a very expensive intern who quits every night. The single highest-leverage decision is making the loop &lt;em&gt;survive the session&lt;/em&gt;. If your accumulated context isn&amp;rsquo;t written down somewhere durable, you don&amp;rsquo;t have a loop. You have a goldfish.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A knowledge base you don&amp;rsquo;t maintain is a liability, not an asset.&lt;/strong&gt; Stale notes are worse than no notes, because they actively mislead. The loop only compounds if you prune as well as append. I treat outdated context like a leak, fix it the moment I notice it, not &amp;ldquo;later.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keep the loop &lt;em&gt;outside&lt;/em&gt; the model.&lt;/strong&gt; The instinct to lean on a single vendor&amp;rsquo;s memory feature or fine-tune is a lock-in trap. The moment your learning lives inside one model&amp;rsquo;s weights or one platform&amp;rsquo;s proprietary store, you&amp;rsquo;ve failed the swap test. Keep evals, traces, and knowledge in formats you control and can carry to the next model.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t confuse activity with accumulation.&lt;/strong&gt; Running a thousand tasks and saving nothing is not a loop. It&amp;rsquo;s a treadmill. Compounding requires that each task leave a deposit. If nothing is being written back, you&amp;rsquo;re just generating tokens, not capital.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-you-should-do"&gt;
&lt;a href="#what-you-should-do" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What you should do
&lt;/h2&gt;
&lt;p&gt;Start owning your loop now, at whatever scale you operate. You don&amp;rsquo;t need a frontier model or an RL pipeline. You need three habits:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Write down your own definition of good.&lt;/strong&gt; A handful of cases that matter to you, that you can re-run. That&amp;rsquo;s your private eval, version zero.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make the context persistent.&lt;/strong&gt; A notes directory, a memory file, anything durable that survives the session. The bar is &amp;ldquo;tomorrow it remembers what today decided.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Prune as ruthlessly as you append.&lt;/strong&gt; The asset isn&amp;rsquo;t volume. It&amp;rsquo;s a loop that stays true.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Nadella&amp;rsquo;s worry is that a few models capture all the value while everyone else&amp;rsquo;s knowledge gets commoditized out from under them. The defense, at every scale, is the same: own the learning loop. The model is the thing you rent. The loop is the thing you keep.&lt;/p&gt;</description></item><item><title>OpenSearch Serverless NextGen: Does Scale-to-Zero Actually Change RAG Costs (And What It Doesn't)</title><link>https://www.yopa.page/blog/2026-06-09-opensearch-nextgen-scale-to-zero.html</link><pubDate>Tue, 09 Jun 2026 02:30:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-06-09-opensearch-nextgen-scale-to-zero.html</guid><description>
&lt;p&gt;AWS &lt;a href="https://aws.amazon.com/blogs/aws/introducing-the-next-generation-of-amazon-opensearch-serverless-for-building-your-agentic-ai-applications/"&gt;rebuilt Amazon OpenSearch Serverless&lt;/a&gt; for agentic AI workloads. The headline numbers are real and worth your attention: idle collections can now scale compute down to &lt;strong&gt;zero&lt;/strong&gt;, burst up to &lt;strong&gt;20x&lt;/strong&gt; in seconds, and the blog claims up to &lt;strong&gt;60% cost savings&lt;/strong&gt; versus the previous capacity model. Check the &lt;a href="https://docs.aws.amazon.com/opensearch-service/latest/developerguide/serverless-create.html"&gt;current collection-generation documentation&lt;/a&gt; before applying the migration steps.&lt;/p&gt;
&lt;p&gt;If you run a RAG stack — especially one where OpenSearch Serverless sits behind a managed knowledge base as the vector store — this lands right in your cost center. But I spiked the migration, and the interesting part isn&amp;rsquo;t the savings. It&amp;rsquo;s the gap between what the announcement promises and what the migration actually costs you. Let me separate the two decisions that this announcement tends to collapse into one.&lt;/p&gt;
&lt;h2 id="what-the-announcement-actually-says"&gt;
&lt;a href="#what-the-announcement-actually-says" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What the announcement actually says
&lt;/h2&gt;
&lt;p&gt;Strip the marketing and NextGen is an &lt;strong&gt;economics and elasticity&lt;/strong&gt; change, not a new retrieval feature:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scale-to-zero.&lt;/strong&gt; The old model kept a floor of OCUs (OpenSearch Compute Units) running per collection — you paid for that floor even when traffic was zero. NextGen lets indexing and search compute drop to zero on idle. You pay storage, not idle compute.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Instant 20x burst.&lt;/strong&gt; When traffic returns, capacity scales back up in seconds rather than minutes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Up to 60% cheaper&lt;/strong&gt; for spiky or low-duty-cycle workloads — which describes almost every internal or dev/QA RAG environment.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;What it is &lt;em&gt;not&lt;/em&gt;: a better retriever, a new ranking model, a new connector, or anything that changes the &lt;em&gt;quality&lt;/em&gt; of what your RAG returns. This is a bill optimization for the vector layer. Keep that framing — it matters for the second half of this post.&lt;/p&gt;
&lt;h2 id="the-migration-trap-the-blog-skips"&gt;
&lt;a href="#the-migration-trap-the-blog-skips" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The migration trap the blog skips
&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s what I found spiking the actual CloudFormation change. NextGen isn&amp;rsquo;t a flag you flip on an existing collection. The way you express &amp;ldquo;NextGen&amp;rdquo; in CloudFormation is by attaching a collection to a new &lt;strong&gt;&lt;code&gt;AWS::OpenSearchServerless::CollectionGroup&lt;/code&gt;&lt;/strong&gt; resource — the group carries &lt;code&gt;CapacityLimits&lt;/code&gt; where you set min OCU to &lt;code&gt;0&lt;/code&gt; for indexing and search (that&amp;rsquo;s your scale-to-zero knob), plus a max ceiling.&lt;/p&gt;
&lt;p&gt;The trap: the property that links a collection to its group is &lt;strong&gt;&lt;code&gt;Update requires: Replace&lt;/code&gt;&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That single line in the CFN docs changes the whole story. Attaching an &lt;em&gt;existing&lt;/em&gt; collection to a group doesn&amp;rsquo;t reconfigure it in place — CloudFormation &lt;strong&gt;deletes and recreates the collection&lt;/strong&gt;. Which means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your vector index is gone and must be rebuilt.&lt;/li&gt;
&lt;li&gt;If a managed knowledge base (e.g. Bedrock Knowledge Bases) consumes that collection, you re-ingest the entire corpus into the fresh collection.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So this is not &amp;ldquo;a one-line config change.&amp;rdquo; It&amp;rsquo;s &amp;ldquo;a new collection plus a full re-index.&amp;rdquo; For an internal knowledge base where the source documents live elsewhere, there&amp;rsquo;s no data-loss risk — but there is real re-ingestion work, multiplied by every region you run in. Budget for it. The announcement won&amp;rsquo;t.&lt;/p&gt;
&lt;h2 id="pitfalls-i-actually-hit"&gt;
&lt;a href="#pitfalls-i-actually-hit" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pitfalls I actually hit
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;aws-cdk-lib&lt;/code&gt; already has the L1.&lt;/strong&gt; I assumed I&amp;rsquo;d need a CDK upgrade to get &lt;code&gt;CfnCollectionGroup&lt;/code&gt; and the &lt;code&gt;collectionGroupName&lt;/code&gt; property. I didn&amp;rsquo;t — a recent-but-not-bleeding-edge &lt;code&gt;aws-cdk-lib&lt;/code&gt; already exposes both. Check your installed version before you bump it; you may already be there.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generation is explicit.&lt;/strong&gt; A collection group accepts &lt;code&gt;Generation: NEXTGEN&lt;/code&gt; (or the CLI&amp;rsquo;s &lt;code&gt;--generation NEXTGEN&lt;/code&gt;); the collection then inherits that generation through its group. It is not a flag you can add to an existing Classic collection in place.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;cdk synth&lt;/code&gt; passing is not validation.&lt;/strong&gt; I got a clean synth producing valid CloudFormation — &lt;code&gt;CollectionGroup&lt;/code&gt; with min 0 / max N, collection wired to it. That proves the template compiles. It proves nothing about whether your downstream consumer accepts the new collection at runtime. (More on this below.)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Type-available is not deploy-validated.&lt;/strong&gt; I confirmed the &lt;code&gt;CollectionGroup&lt;/code&gt; resource type is available across all the major regions I care about. That removes regional availability as a blocker — but &amp;ldquo;the API knows about the type&amp;rdquo; and &amp;ldquo;I deployed it and the knowledge base indexed against it&amp;rdquo; are two different gates. Don&amp;rsquo;t let the first one let you skip the second.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Scale-to-zero trades against cold start.&lt;/strong&gt; Zero idle compute is free money for dev/QA, where nobody&amp;rsquo;s waiting. On a customer-facing path, the first query after idle pays a warm-up. Whether that&amp;rsquo;s acceptable is a latency conversation, not a cost one — and it&amp;rsquo;s the reason I&amp;rsquo;d gate the change to non-prod first and prove the cold-start behavior before touching production.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-decision-this-announcement-does-not-make-for-you"&gt;
&lt;a href="#the-decision-this-announcement-does-not-make-for-you" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The decision this announcement does NOT make for you
&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s the trap I see teams walk into. A vector-store cost announcement arrives, and it gets read as a signal to &lt;strong&gt;switch RAG engines&lt;/strong&gt; — &amp;ldquo;should we move off our managed search service onto OpenSearch?&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Those are different decisions, and this announcement only touches one of them.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&amp;ldquo;Should I move my existing OpenSearch vector store to NextGen?&amp;rdquo;&lt;/strong&gt; — Yes, plausibly. It&amp;rsquo;s a cost optimization on infrastructure you already run. The analysis is the migration cost (re-index) versus the idle-compute savings. For low-duty-cycle environments, that math is easy.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;ldquo;Should I replace my managed RAG service with OpenSearch?&amp;rdquo;&lt;/strong&gt; — This announcement is &lt;em&gt;irrelevant&lt;/em&gt; to that question. Engine choice is about retrieval quality, relevance tuning, ACL and multi-tenant filtering parity, connector ecosystem, and operational burden. A 60% bill reduction on the vector layer tells you nothing about whether OpenSearch retrieves &lt;em&gt;better&lt;/em&gt; for your corpus, or whether you can reproduce your current access-control model. If you&amp;rsquo;re evaluating an engine switch, evaluate &lt;em&gt;those&lt;/em&gt; axes — and don&amp;rsquo;t let a cost headline stand in for that work.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Conflating the two is how you end up justifying a months-long engine migration with a number that was only ever about idle compute.&lt;/p&gt;
&lt;h2 id="what-you-should-do"&gt;
&lt;a href="#what-you-should-do" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What you should do
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;If you already run OpenSearch Serverless as a vector store:&lt;/strong&gt; spike the NextGen migration in dev. Wire a &lt;code&gt;CollectionGroup&lt;/code&gt; with min OCU 0, attach the collection, and &lt;strong&gt;actually deploy it&lt;/strong&gt; — then confirm your knowledge base re-indexes and retrieves against the new collection. Synth passing is not that proof.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plan for re-ingestion, not a config flip.&lt;/strong&gt; The collection gets replaced. Know your re-index cost per environment before you commit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gate it to non-prod first.&lt;/strong&gt; Capture the scale-to-zero savings where idle is free, and prove cold-start latency before you put a customer-facing path behind it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t let this decide your RAG engine.&lt;/strong&gt; If you&amp;rsquo;re weighing a managed-search-to-OpenSearch move, run the retrieval-quality and access-control evaluation that decision actually requires. This announcement isn&amp;rsquo;t input to it.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;NextGen is a genuinely good change for the right workload. Just be honest about which decision it&amp;rsquo;s answering — and which one it&amp;rsquo;s quietly tempting you to skip.&lt;/p&gt;</description></item><item><title>Interactive Shells in Bedrock AgentCore Runtime — The Debugging Blind Spot Just Closed</title><link>https://www.yopa.page/blog/2026-06-08-agentcore-interactive-shells.html</link><pubDate>Mon, 08 Jun 2026 02:30:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-06-08-agentcore-interactive-shells.html</guid><description>
&lt;p&gt;If you run agents on Bedrock AgentCore Runtime, you already know the shape of the problem. Your agent isn&amp;rsquo;t a Lambda you can re-invoke locally — it&amp;rsquo;s a managed container (possibly in a private VPC subnet, if you configure VPC networking), holding session state you can&amp;rsquo;t see, talking to an MCP gateway and a memory store you can only observe through logs. When a session misbehaves, your entire toolbox has been: &lt;em&gt;tail CloudWatch and guess.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;In June 2026, AWS &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-get-started-command-shell.html"&gt;closed that gap&lt;/a&gt;: AgentCore Runtime now supports &lt;strong&gt;interactive shells&lt;/strong&gt; into running agent sessions, via a new &lt;code&gt;InvokeAgentRuntimeCommandShell&lt;/code&gt; API. You can attach a real terminal to a live session and look around.&lt;/p&gt;
&lt;p&gt;This sounds like a developer-experience footnote. For anyone running agents as containers, it&amp;rsquo;s the difference between forensics-by-logfile and walking into the room.&lt;/p&gt;
&lt;h2 id="why-this-was-a-black-box"&gt;
&lt;a href="#why-this-was-a-black-box" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why this was a black box
&lt;/h2&gt;
&lt;p&gt;The thing that makes AgentCore Runtime good in production is the same thing that made it opaque to debug:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The agent runs as a &lt;strong&gt;container in a private subnet&lt;/strong&gt; — no public ingress, no SSH, no direct reach.&lt;/li&gt;
&lt;li&gt;Session state lives &lt;strong&gt;inside the running process&lt;/strong&gt;: environment variables, working directory, in-memory context, whatever the agent has loaded.&lt;/li&gt;
&lt;li&gt;Everything you knew about a session came &lt;strong&gt;after the fact&lt;/strong&gt;, from logs — and only what you remembered to log.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;So when a tool call silently failed, or a cross-account role assumption didn&amp;rsquo;t fire, or the wrong context got injected into a session, you couldn&amp;rsquo;t go &lt;em&gt;look&lt;/em&gt;. You added log lines, redeployed, and waited for it to happen again. For a class of bugs that only reproduce under specific session state, that loop is brutal.&lt;/p&gt;
&lt;h2 id="what-landed"&gt;
&lt;a href="#what-landed" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What landed
&lt;/h2&gt;
&lt;p&gt;The new API gives you a persistent, PTY-backed terminal over WebSocket. The headline capabilities:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;InvokeAgentRuntimeCommandShell&lt;/code&gt;&lt;/strong&gt; — opens an interactive shell into a running agent session.&lt;/li&gt;
&lt;li&gt;Complements the existing &lt;strong&gt;&lt;code&gt;InvokeAgentRuntimeCommand&lt;/code&gt;&lt;/strong&gt; — which runs a single one-shot command and returns.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Full terminal features&lt;/strong&gt;: colors, tab completion, &lt;code&gt;Ctrl+C&lt;/code&gt;, terminal resize, automatic reconnect.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Persistent state across commands&lt;/strong&gt;: environment variables, working directory, and command history survive between commands in the same shell.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Reconnectable&lt;/strong&gt;: sessions are addressed by a runtime session ID plus a shell ID, so you can drop and re-attach to the &lt;em&gt;same&lt;/em&gt; shell.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Up to 10 concurrent shells&lt;/strong&gt; per agent runtime — parallel debugging across sessions.&lt;/li&gt;
&lt;li&gt;Access via the AgentCore CLI: &lt;code&gt;agentcore exec --it --runtime &amp;lt;runtime-arn&amp;gt;&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="one-shot-vs-interactive--pick-the-right-one"&gt;
&lt;a href="#one-shot-vs-interactive--pick-the-right-one" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
One-shot vs interactive — pick the right one
&lt;/h2&gt;
&lt;p&gt;The two APIs answer different questions. Don&amp;rsquo;t reach for the interactive shell when a one-shot command would do.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;InvokeAgentRuntimeCommand&lt;/code&gt;&lt;/th&gt;
&lt;th&gt;&lt;code&gt;InvokeAgentRuntimeCommandShell&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Shape&lt;/td&gt;
&lt;td&gt;Single command, returns output&lt;/td&gt;
&lt;td&gt;Persistent interactive session&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;State&lt;/td&gt;
&lt;td&gt;Stateless per call&lt;/td&gt;
&lt;td&gt;Env vars / cwd / history persist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Transport&lt;/td&gt;
&lt;td&gt;Request/response&lt;/td&gt;
&lt;td&gt;WebSocket (PTY)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reconnect&lt;/td&gt;
&lt;td&gt;N/A&lt;/td&gt;
&lt;td&gt;Re-attach by session + shell ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Best for&lt;/td&gt;
&lt;td&gt;Scripted checks, CI probes, &amp;ldquo;is X set?&amp;rdquo;&lt;/td&gt;
&lt;td&gt;Live exploration, multi-step investigation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;If you&amp;rsquo;re scripting a health check or a deterministic probe, &lt;code&gt;Command&lt;/code&gt; is cheaper and auditable. The shell is for the &lt;em&gt;I don&amp;rsquo;t know what I&amp;rsquo;m looking for yet&lt;/em&gt; case.&lt;/p&gt;
&lt;h2 id="when-you-actually-reach-for-the-shell"&gt;
&lt;a href="#when-you-actually-reach-for-the-shell" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
When you actually reach for the shell
&lt;/h2&gt;
&lt;p&gt;This isn&amp;rsquo;t an everyday tool. It earns its keep on the bugs that don&amp;rsquo;t reproduce on demand:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Verify injected configuration.&lt;/strong&gt; Is the runtime actually seeing the gateway URL, memory ID, and account config you think you wired in? &lt;code&gt;printenv&lt;/code&gt; inside the live session beats reading CDK output and hoping.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Diagnose connectivity from inside the network.&lt;/strong&gt; The container sits in a private subnet. From the shell you can &lt;code&gt;curl&lt;/code&gt; the MCP gateway endpoint, test a cross-account &lt;code&gt;sts assume-role&lt;/code&gt;, or confirm a downstream agent (A2A) is reachable — instead of inferring failure from a stack trace.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inspect session context.&lt;/strong&gt; In a multi-tenant runtime, &amp;ldquo;did this session boot with the &lt;em&gt;right&lt;/em&gt; tenant context?&amp;rdquo; is a question you&amp;rsquo;ve never been able to answer at runtime. Now you can.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parallel investigation.&lt;/strong&gt; Ten concurrent shells means you can pin one session, compare against a known-good one, and not lose your place when you step away — the reconnect-by-ID semantics keep the shell alive.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="pitfalls-and-the-security-conversation"&gt;
&lt;a href="#pitfalls-and-the-security-conversation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pitfalls and the security conversation
&lt;/h2&gt;
&lt;p&gt;A live shell into a running agent is powerful, which is exactly why it&amp;rsquo;s dangerous. Before you enable it, walk through these:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A shell into a production agent session is a privilege.&lt;/strong&gt; It&amp;rsquo;s interactive code execution inside a process that holds real credentials, real session data, and (in multi-tenant setups) potentially other tenants&amp;rsquo; context. Treat it like SSH-into-prod, because that&amp;rsquo;s what it is.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gate it with IAM, not goodwill.&lt;/strong&gt; The action that opens a shell is distinct from the action that invokes the agent. Grant &lt;code&gt;InvokeAgentRuntimeCommandShell&lt;/code&gt; (and the one-shot &lt;code&gt;Command&lt;/code&gt; variant) explicitly, to a small set of principals, and &lt;em&gt;deny it in production&lt;/em&gt; unless you have a break-glass process. Don&amp;rsquo;t let it ride along on your existing invoke permissions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Default to dev/QA.&lt;/strong&gt; The overwhelming majority of the value is in non-prod, where you&amp;rsquo;re chasing reproductions. Enable the shell there freely; in prod, make it an audited, time-boxed exception.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check your CLI version.&lt;/strong&gt; &lt;code&gt;agentcore exec --it&lt;/code&gt; needs an AgentCore CLI build that speaks the new API. Confirm before you plan a debugging session around it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mind the operational limits.&lt;/strong&gt; A shell connection can last up to one hour, a frame is limited to 64 KB, and reconnecting can replay up to 256 KB of buffered output. Runtimes also have an idle session timeout (for example, 15 minutes), so know whether an attach resets that clock. CloudTrail records API metadata, not shell stdin/stdout.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-you-should-do"&gt;
&lt;a href="#what-you-should-do" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What you should do
&lt;/h2&gt;
&lt;p&gt;If you run agents on AgentCore Runtime, the move is small and worth making:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Add &lt;code&gt;bedrock-agentcore:InvokeAgentRuntimeCommandShell&lt;/code&gt; (and &lt;code&gt;InvokeAgentRuntimeCommand&lt;/code&gt;) to a &lt;strong&gt;dev/QA-scoped&lt;/strong&gt; IAM policy, attached to your team&amp;rsquo;s debugging principals — not to the agent&amp;rsquo;s own runtime role.&lt;/li&gt;
&lt;li&gt;Keep it &lt;strong&gt;denied in production&lt;/strong&gt; behind a documented break-glass exception.&lt;/li&gt;
&lt;li&gt;Confirm your AgentCore CLI supports &lt;code&gt;exec --it&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The next time a session bug won&amp;rsquo;t reproduce, stop adding log lines. Attach, look, and fix.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The pattern shift is the whole story: agent debugging goes from &lt;em&gt;guess-from-logs&lt;/em&gt; to &lt;em&gt;walk-in-and-look&lt;/em&gt;. That&amp;rsquo;s worth a one-line IAM change and a guardrail.&lt;/p&gt;</description></item><item><title>Step Functions × AgentCore: What the Managed Harness Integration Actually Supports</title><link>https://www.yopa.page/blog/2026-06-04-step-functions-agentcore-async-pipelines.html</link><pubDate>Thu, 04 Jun 2026 11:30:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-06-04-step-functions-agentcore-async-pipelines.html</guid><description>
&lt;p&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/06/aws-step-functions-agentcore/"&gt;AWS announced&lt;/a&gt; an optimized Step Functions integration for the managed Bedrock AgentCore harness in June 2026. The important detail is easy to miss: the resource is &lt;code&gt;arn:aws:states:::bedrockagentcore:invokeHarness&lt;/code&gt;, and the integration currently supports &lt;strong&gt;Request Response&lt;/strong&gt; only.&lt;/p&gt;
&lt;p&gt;That is still useful for a state machine that already needs a managed AgentCore turn. It is not a drop-in &lt;code&gt;.sync&lt;/code&gt; replacement for every &lt;code&gt;InvokeAgentRuntime&lt;/code&gt; call, and it does not make an eight-hour agent invocation a single Step Functions task.&lt;/p&gt;
&lt;h2 id="the-shape-we-keep-ending-up-with"&gt;
&lt;a href="#the-shape-we-keep-ending-up-with" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The shape we keep ending up with
&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re building agentic features on AWS for a real product, the architecture converges on something close to this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;POST /something/runs → 202 + requestId
↓
Step Functions starts
↓
Lambda(call agent + write result)
↓
DynamoDB updated
↓
GET /something/runs/{id} → status + output
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The state machine exists because the agent call is too slow to hold an HTTP connection open for. The Lambda may be thin, but it often owns session IDs, payload storage, retries, callback tokens, and the durable result. The optimized integration can remove that wrapper for a bounded harness turn, but it does not remove those workflow responsibilities automatically.&lt;/p&gt;
&lt;p&gt;It works. It also has four specific problems that don&amp;rsquo;t go away no matter how clean your code is.&lt;/p&gt;
&lt;h2 id="four-problems-this-pattern-actually-has"&gt;
&lt;a href="#four-problems-this-pattern-actually-has" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Four problems this pattern actually has
&lt;/h2&gt;
&lt;h3 id="1-a-task-limit-still-decides-how-long-the-optimized-turn-can-run"&gt;
&lt;a href="#1-a-task-limit-still-decides-how-long-the-optimized-turn-can-run" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1. A task limit still decides how long the optimized turn can run
&lt;/h3&gt;
&lt;p&gt;This one is backwards. The agent&amp;rsquo;s reasoning budget should be a product decision — &amp;ldquo;this action involves five tool calls and a long context, so it might take 8 minutes&amp;rdquo; — but in practice, the limit is whatever Lambda lets you do. Anything longer needs ECS, Fargate, or a chain of Lambdas that hand off state, all of which are work nobody actually wants to do.&lt;/p&gt;
&lt;p&gt;The optimized resource is subject to the Step Functions integration limit of &lt;strong&gt;15 minutes&lt;/strong&gt;. Increasing &lt;code&gt;TimeoutSeconds&lt;/code&gt; above that does not turn it into an eight-hour task. If the task times out, the harness can continue running, so you need an explicit cleanup or reconciliation path.&lt;/p&gt;
&lt;h3 id="2-multi-step-agent-workflows-fail-atomically"&gt;
&lt;a href="#2-multi-step-agent-workflows-fail-atomically" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2. Multi-step agent workflows fail atomically
&lt;/h3&gt;
&lt;p&gt;If your action does plan → fan-out tool calls → reduce → validate, all of that runs inside one agent loop, inside one Lambda. Any failure — a tool that timed out, a downstream service that returned 503, a model that produced malformed JSON on the third turn — restarts the whole thing.&lt;/p&gt;
&lt;p&gt;The state machine layer above it is useless for this, because from its perspective the Lambda either succeeded or didn&amp;rsquo;t. There&amp;rsquo;s no &amp;ldquo;the plan finished but the validation step blew up, retry just the validation.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;You can still make the workflow a workflow, but you do it with separate states rather than one automatically decomposed agent call:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Plan(agent)
→ Map(tool calls in parallel)
→ Reduce(agent)
→ Validate(agent)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Each step has its own retry, catch, and checkpoint. A failure in &lt;code&gt;Validate&lt;/code&gt; re-runs &lt;code&gt;Validate&lt;/code&gt;, not the whole chain.&lt;/p&gt;
&lt;h3 id="3-external-job-polling-is-a-pattern-everyone-reimplements"&gt;
&lt;a href="#3-external-job-polling-is-a-pattern-everyone-reimplements" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
3. External job polling is a pattern everyone reimplements
&lt;/h3&gt;
&lt;p&gt;Textract async, Bedrock batch inference, anything with &amp;ldquo;submit job → poll for completion&amp;rdquo; — every team writes the same loop. Configurable interval, max attempts, status mapping, exit on failure. It&amp;rsquo;s never quite the same code twice.&lt;/p&gt;
&lt;p&gt;For AgentCore, the optimized harness task is Request Response, not a general &lt;code&gt;.sync&lt;/code&gt; poller. If you need submit-and-wait semantics, use one of the explicit callback or direct-SDK patterns described below and keep the session, timeout, and result state in your design.&lt;/p&gt;
&lt;h3 id="4-agent-execution-is-invisible-at-the-workflow-layer"&gt;
&lt;a href="#4-agent-execution-is-invisible-at-the-workflow-layer" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
4. Agent execution is invisible at the workflow layer
&lt;/h3&gt;
&lt;p&gt;Right now, if a multi-step agent call goes wrong, your CloudWatch logs are a wall of &lt;code&gt;{&amp;quot;level&amp;quot;: &amp;quot;INFO&amp;quot;, &amp;quot;agent&amp;quot;: ...}&lt;/code&gt; lines and you&amp;rsquo;re greping for the failure. Per-step cost? Aggregated at the Lambda level. X-Ray traces? They cover the Lambda, not the agent&amp;rsquo;s internal turns.&lt;/p&gt;
&lt;p&gt;When the agent invocation is a state machine task, the workflow execution view shows it as a step. Cost attributes per step. X-Ray traces span the agent call. Failure modes have names again.&lt;/p&gt;
&lt;h2 id="what-the-optimized-agentcore-task-changes"&gt;
&lt;a href="#what-the-optimized-agentcore-task-changes" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What the optimized AgentCore task changes
&lt;/h2&gt;
&lt;p&gt;Concretely:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Lambda wrapper calls &lt;code&gt;invoke_agent_runtime&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Optimized &lt;code&gt;arn:aws:states:::bedrockagentcore:invokeHarness&lt;/code&gt; task&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lambda has a 15-minute timeout&lt;/td&gt;
&lt;td&gt;Optimized task is also limited to 15 minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Agent loop is one opaque call&lt;/td&gt;
&lt;td&gt;Separate states are still required for independent retry&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hand-written polling/callback code&lt;/td&gt;
&lt;td&gt;Still required for long-running or callback workflows&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lambda logs only&lt;/td&gt;
&lt;td&gt;Final text plus execution/CloudWatch harness trace link&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Custom approval state&lt;/td&gt;
&lt;td&gt;&lt;code&gt;waitForTaskToken&lt;/code&gt; remains an explicit pattern outside this task&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The approval case still matters, but it is not supplied by the optimized harness resource. A state machine can pause with &lt;code&gt;waitForTaskToken&lt;/code&gt;, an external system can call back with the token, and a later state can invoke AgentCore. Keep the approval token and business state in a durable store rather than assuming the harness session is the workflow record.&lt;/p&gt;
&lt;h2 id="patterns-for-genuinely-asynchronous-work"&gt;
&lt;a href="#patterns-for-genuinely-asynchronous-work" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Patterns for genuinely asynchronous work
&lt;/h2&gt;
&lt;p&gt;When an agent must run longer than the task limit, or a human must approve a result, use an explicit callback pattern instead of treating the optimized task as durable:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Lambda dispatcher + callback token.&lt;/strong&gt; A Lambda starts or invokes the AgentCore runtime, gives the agent a Step Functions task token, and the agent or a callback handler calls &lt;code&gt;SendTaskSuccess&lt;/code&gt;/&lt;code&gt;SendTaskFailure&lt;/code&gt; when the durable work finishes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Direct AWS SDK integration.&lt;/strong&gt; Step Functions can call &lt;code&gt;arn:aws:states:::aws-sdk:bedrockagentcore:invokeAgentRuntime&lt;/code&gt; without a Lambda in the request path. You still own the session ID, timeout, polling/callback decision, and result persistence.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Durable function callback.&lt;/strong&gt; A Lambda durable function can hold the orchestration and resume from a callback while each AgentCore invocation remains a bounded unit of work.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Use a stable session ID, keep large inputs and outputs in S3 rather than the 256 KB Step Functions state, and set heartbeat/timeouts that match the business operation. A session that may last days is not the same thing as a durable business process: persist process state and let Step Functions own the wait.&lt;/p&gt;
&lt;h2 id="decision-tree--should-you-actually-migrate"&gt;
&lt;a href="#decision-tree--should-you-actually-migrate" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Decision tree — should you actually migrate?
&lt;/h2&gt;
&lt;p&gt;Not every async agent call benefits from this.&lt;/p&gt;
&lt;p&gt;✅ &lt;strong&gt;Worth migrating&lt;/strong&gt; if:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You already run agents inside Step Functions via Lambda&lt;/li&gt;
&lt;li&gt;You need a bounded harness turn and its final text/usage result in a state machine&lt;/li&gt;
&lt;li&gt;You already run agents inside Step Functions via Lambda and want to remove only that wrapper&lt;/li&gt;
&lt;li&gt;You can split a workflow into explicit states with independent retry&lt;/li&gt;
&lt;li&gt;You can keep genuinely long-running or approval work in one of the callback patterns above&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Cost-first before migrating&lt;/strong&gt; if:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You&amp;rsquo;re using a direct SDK (Strands, raw Bedrock) inside Lambda. Moving to AgentCore Runtime is its own deployment and operational change. The integration assumes you&amp;rsquo;ve already adopted AgentCore.&lt;/li&gt;
&lt;li&gt;Your async pipeline is small (one Lambda, one DynamoDB write) and the rewrite is bigger than the win.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;❌ &lt;strong&gt;Don&amp;rsquo;t bother&lt;/strong&gt; if:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The agent call is short (&amp;lt; 1 min), single-turn, and never fails interestingly. Lambda + SDK is fine.&lt;/li&gt;
&lt;li&gt;You don&amp;rsquo;t actually have a state machine. Adding one to use this integration is putting the cart before the horse.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="pitfalls-to-expect"&gt;
&lt;a href="#pitfalls-to-expect" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pitfalls to expect
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Step Functions state size limit is 256 KB.&lt;/strong&gt; This already affects anyone passing large documents through SF. With agent inputs/outputs in the loop, keep payloads in S3 and pass references. The integration does not relax the limit.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Do not write &lt;code&gt;InvokeAgentRuntime.sync&lt;/code&gt; by analogy.&lt;/strong&gt; Use the documented &lt;code&gt;invokeHarness&lt;/code&gt; resource for the optimized integration; &lt;code&gt;.sync&lt;/code&gt; and &lt;code&gt;waitForTaskToken&lt;/code&gt; are not supported there.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Task timeout and agent timeout are different clocks.&lt;/strong&gt; A Step Functions timeout can leave the harness running. Add a cancellation, idempotency, or reconciliation plan.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AgentCore Runtime region availability.&lt;/strong&gt; As of launch, AgentCore is in fewer regions than Step Functions. If your product runs in &lt;code&gt;me-central-1&lt;/code&gt; or &lt;code&gt;ap-northeast-1&lt;/code&gt;, check before you redesign. Cross-region AgentCore invocation from SF is a separate question.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cold start moves, it doesn&amp;rsquo;t disappear.&lt;/strong&gt; You stop paying for a Lambda wrapper, but you still need to measure AgentCore runtime behavior on a real workload.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IAM gets a new principal in the trust chain.&lt;/strong&gt; The state machine&amp;rsquo;s execution role needs the documented AgentCore permission for the resource you call. If your platform has tag-based access controls, the state machine role needs the right tag, not just the old Lambda role.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Observability changes shape.&lt;/strong&gt; The optimized task returns final assistant text and aggregated usage, not every tool/reasoning block. Keep the CloudWatch harness trace or your own durable evidence for detailed debugging.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-this-actually-means"&gt;
&lt;a href="#what-this-actually-means" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What this actually means
&lt;/h2&gt;
&lt;p&gt;Before this integration, many teams put a thin Lambda around every AgentCore call. The optimized task removes that wrapper for one bounded harness turn, but it does not make every runtime invocation a durable Step Functions task.&lt;/p&gt;
&lt;p&gt;AgentCore-as-task is therefore a useful narrow contract: final-text handoff, aggregated usage, and a link to the harness trace. Long-running execution, human approval, and partial retry still belong in explicit Step Functions states and callback patterns.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;ve been deferring an agentic feature because &amp;ldquo;the async story is messy,&amp;rdquo; it is worth a fresh look. Just choose the optimized task only when its Request Response and 15-minute contract actually fits.&lt;/p&gt;</description></item><item><title>ElastiCache Just Got Durable — What That Means for RAG and Agent Orchestration</title><link>https://www.yopa.page/blog/2026-06-03-elasticache-durability-rag-agent-patterns.html</link><pubDate>Wed, 03 Jun 2026 12:00:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-06-03-elasticache-durability-rag-agent-patterns.html</guid><description>
&lt;p&gt;On June 2, 2026, AWS &lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/06/durability-amazon-elasticache/"&gt;announced durability for Amazon ElastiCache&lt;/a&gt; starting with node-based Valkey 9.0. That sounds like a small line item, but it changes what ElastiCache is &lt;em&gt;for&lt;/em&gt;. You can pick, per cluster, whether writes are durable across AZs, so some data-store workloads no longer need to use DynamoDB merely as a safety net. Read the &lt;a href="https://docs.aws.amazon.com/AmazonElastiCache/latest/dg/durability.html"&gt;current durability documentation&lt;/a&gt; for engine, eviction, and failure semantics before treating Valkey as a source of record.&lt;/p&gt;
&lt;p&gt;This post is me thinking out loud about where that matters. I&amp;rsquo;m a backend/platform engineer whose last real cache project was Redis in n8n. Most of my recent work is AWS AI infra — RAG pipelines, agent orchestration with Step Functions, dead-letter retries. Cache patterns sit at the seam of all of those, and I want to make sure I&amp;rsquo;m not under-using them on the next project.&lt;/p&gt;
&lt;h2 id="what-actually-changed"&gt;
&lt;a href="#what-actually-changed" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What actually changed
&lt;/h2&gt;
&lt;p&gt;Two new write modes, on top of the existing in-memory speed:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Write latency&lt;/th&gt;
&lt;th&gt;Data loss window&lt;/th&gt;
&lt;th&gt;Use when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Synchronous&lt;/td&gt;
&lt;td&gt;single-digit ms&lt;/td&gt;
&lt;td&gt;zero (persisted to ≥2 AZs before ack)&lt;/td&gt;
&lt;td&gt;money, identity, state you cannot replay&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Asynchronous&lt;/td&gt;
&lt;td&gt;microseconds (free)&lt;/td&gt;
&lt;td&gt;up to 10s on AZ failure&lt;/td&gt;
&lt;td&gt;hot path you can rebuild from a system of record&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(legacy) non-durable&lt;/td&gt;
&lt;td&gt;microseconds&lt;/td&gt;
&lt;td&gt;everything since last snapshot&lt;/td&gt;
&lt;td&gt;true ephemeral cache&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Reads stay at microseconds in all three. The transactional log lives across multiple AZs, so failover, restart, and recovery can preserve acknowledged writes in synchronous mode. Asynchronous mode still allows up to 10 seconds of successful writes to be lost during a failure, and durable clusters can still evict TTL keys when memory is under pressure.&lt;/p&gt;
&lt;p&gt;The headline phrase from the AWS blurb — &amp;ldquo;AI agent long-term memory, AI agent workflow state, knowledge bases for RAG applications&amp;rdquo; — is unusually direct. AWS is telling you what they expect people to build with this.&lt;/p&gt;
&lt;h2 id="pattern-1--rag-semantic-cache"&gt;
&lt;a href="#pattern-1--rag-semantic-cache" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pattern 1 — RAG semantic cache
&lt;/h2&gt;
&lt;p&gt;In a vanilla RAG path, every user query embeds → searches a vector store → re-ranks → calls the LLM. The vector search and the LLM call are the two expensive steps. Both are deterministic enough to cache.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;User query
→ embed (cheap)
→ semantic cache lookup by embedding similarity ← ElastiCache
hit → return cached answer (microseconds)
miss → vector store + LLM call
→ write {embedding hash : answer} back ← ElastiCache
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Key shape options:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Exact key&lt;/strong&gt;: hash of the normalized prompt. Trivial to implement, low hit rate.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Semantic key&lt;/strong&gt;: store the embedding and approximate-match on cosine similarity above some threshold. Higher hit rate but you need a vector index &lt;em&gt;inside&lt;/em&gt; the cache. Valkey/Redis modules support this; you can also keep a small HNSW in memory.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Why durability matters here: if your cache is ephemeral, every cluster restart cold-starts your LLM bill. Async durability is plenty — losing 10 seconds of cached answers is not a correctness problem, just a brief cost spike. Sync durability is overkill. &lt;strong&gt;Default async, and remember you no longer have to choose between &amp;ldquo;cache&amp;rdquo; and &amp;ldquo;real store.&amp;rdquo;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pitfall I&amp;rsquo;d worry about: cache poisoning when the upstream RAG pipeline returns a hallucinated or stale answer once. That answer can sit in cache for the TTL and serve to other users. Always include a content-version key (model name + prompt template version + index version) so a deploy invalidates without manual flush.&lt;/p&gt;
&lt;h2 id="pattern-2--agent-workflow-state-in-step-functions"&gt;
&lt;a href="#pattern-2--agent-workflow-state-in-step-functions" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pattern 2 — Agent workflow state in Step Functions
&lt;/h2&gt;
&lt;p&gt;Step Functions gives you &lt;code&gt;Map&lt;/code&gt;, &lt;code&gt;Parallel&lt;/code&gt;, &lt;code&gt;Wait&lt;/code&gt;, retry policies, and &lt;code&gt;.waitForTaskToken&lt;/code&gt; for human-in-the-loop. What it doesn&amp;rsquo;t give you cheaply is:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mid-execution scratchpad bigger than the 256 KB state payload&lt;/li&gt;
&lt;li&gt;Cross-execution memory (&amp;ldquo;this agent already saw this customer last hour&amp;rdquo;)&lt;/li&gt;
&lt;li&gt;Fast lookups that aren&amp;rsquo;t worth a DynamoDB round trip&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Pre-durability ElastiCache solved the speed problem but felt risky for anything you couldn&amp;rsquo;t reconstruct. With durable Valkey, the pattern becomes clean:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Execution scoped state&lt;/strong&gt; (lives for one Step Functions run): key &lt;code&gt;agent:exec:&amp;lt;exec-arn&amp;gt;:&amp;lt;slot&amp;gt;&lt;/code&gt;, TTL = max execution duration + buffer. Use sync writes if losing the slot would corrupt downstream steps; async otherwise.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Agent long-term memory&lt;/strong&gt; (lives across runs): key &lt;code&gt;agent:user:&amp;lt;user-id&amp;gt;:facts&lt;/code&gt;, no TTL or a long one. Sync writes — this is the memory the user expects to persist.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tool-call cache&lt;/strong&gt; (idempotency for expensive tools): key &lt;code&gt;tool:&amp;lt;tool-name&amp;gt;:&amp;lt;arg-hash&amp;gt;&lt;/code&gt;, short TTL. Async is fine.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Step Functions Lambda tasks read/write this directly. You no longer need a &amp;ldquo;memory service&amp;rdquo; Lambda in front. ElastiCache is the memory service.&lt;/p&gt;
&lt;h2 id="pattern-3--dlq-retry-coordinator"&gt;
&lt;a href="#pattern-3--dlq-retry-coordinator" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pattern 3 — DLQ retry coordinator
&lt;/h2&gt;
&lt;p&gt;This is the one I keep underbuilding. SQS dead-letter queues are great at catching failures, but the recovery story is usually a Lambda that drains the DLQ and reprocesses blindly. That works until you have &lt;em&gt;correlated&lt;/em&gt; failures — same downstream API outage hits 1000 messages, you reprocess them all, they all fail again, you back off and retry, repeat.&lt;/p&gt;
&lt;p&gt;A cache-coordinated retry layer fixes this:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;DLQ message arrives
→ cache lookup: dlq:&amp;lt;failure-fingerprint&amp;gt; ← ElastiCache
- count &amp;gt; N in window → don&amp;#39;t retry yet, set circuit-open flag
- count ≤ N → increment, retry, on success delete key
→ on circuit-open: skip retries until TTL expires
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The fingerprint is whatever isolates the failure class — downstream service ID + error code, for example. The cache holds the rolling counter and the open-circuit flag. Sync durability matters here: if the counter resets on AZ failover, you lose your back-pressure and slam the recovering downstream the moment it comes back. This is a place where the new sync mode pays for itself.&lt;/p&gt;
&lt;p&gt;Bonus: the same cache can hold the per-key idempotency token so a retry that &lt;em&gt;did&lt;/em&gt; partially succeed doesn&amp;rsquo;t double-charge a payment or double-write a record.&lt;/p&gt;
&lt;h2 id="choosing-between-sync-async-and-dynamodb"&gt;
&lt;a href="#choosing-between-sync-async-and-dynamodb" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Choosing between sync, async, and DynamoDB
&lt;/h2&gt;
&lt;p&gt;A rough heuristic I&amp;rsquo;m landing on:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;Pick&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Rebuildable, hot path, cost matters&lt;/td&gt;
&lt;td&gt;ElastiCache async&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Correctness-critical, low write rate, microsecond reads&lt;/td&gt;
&lt;td&gt;ElastiCache sync&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Audit trail, queries beyond key lookup, infrequent access&lt;/td&gt;
&lt;td&gt;DynamoDB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Big blob, write-once-read-many&lt;/td&gt;
&lt;td&gt;S3, with ElastiCache pointer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;DynamoDB doesn&amp;rsquo;t go away. It&amp;rsquo;s still the right answer for anything you&amp;rsquo;ll query by attribute, paginate, or audit. ElastiCache durability isn&amp;rsquo;t a DynamoDB replacement — it&amp;rsquo;s a way to stop using DynamoDB &lt;em&gt;as a poor cache&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="what-im-doing-next"&gt;
&lt;a href="#what-im-doing-next" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What I&amp;rsquo;m doing next
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Adding a semantic-cache layer in front of the RAG step in our internal agent. Async writes, embedding-hash key, content-version included.&lt;/li&gt;
&lt;li&gt;Replacing a small DynamoDB &amp;ldquo;agent scratchpad&amp;rdquo; table with a Valkey cluster for one workflow as a proof-of-concept. Sync writes for the slot the next state reads.&lt;/li&gt;
&lt;li&gt;Sketching the DLQ coordinator pattern for our most retry-loud Step Function. This one I want to write a small spec for before building.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If the last time you touched a cache was Redis in some workflow tool, this announcement is a reason to revisit. The &amp;ldquo;cache vs database&amp;rdquo; line just moved.&lt;/p&gt;</description></item><item><title>Why MCP Clients on AgentCore Gateway Need OAuth Auth Code Flow (Not M2M)</title><link>https://www.yopa.page/blog/2026-06-03-agentcore-gateway-mcp-oauth-auth-code.html</link><pubDate>Wed, 03 Jun 2026 11:45:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-06-03-agentcore-gateway-mcp-oauth-auth-code.html</guid><description>
&lt;p&gt;AWS &lt;a href="https://aws.amazon.com/blogs/machine-learning/building-a-secure-auth-code-flow-setup-using-agentcore-gateway-with-mcp-clients/"&gt;published a walkthrough&lt;/a&gt; for wiring OAuth 2.0 authorization code flow between an MCP client (Cursor, Claude Desktop, your own) and a Bedrock AgentCore Gateway acting as a tool server. It&amp;rsquo;s a good tutorial. What it doesn&amp;rsquo;t spell out — and what bit me when I started thinking about exposing internal tools to outside MCP clients — is the underlying decision: &lt;strong&gt;when do you actually need three-legged OAuth here, and what breaks if you cheap out with client credentials?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;This post is the decision tree, not the tutorial.&lt;/p&gt;
&lt;h2 id="the-setup-briefly"&gt;
&lt;a href="#the-setup-briefly" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The setup, briefly
&lt;/h2&gt;
&lt;p&gt;AgentCore Gateway sits between an MCP client and your Lambda-backed tools:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;MCP Client (Cursor / Claude Desktop / custom)
│ (MCP over HTTP+SSE, with bearer token)
▼
AgentCore Gateway ──► Cognito / Okta / Auth0 (token verification)
│
▼ (signed invoke)
Lambda Tools ──► DynamoDB / Salesforce / Jira / whatever
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Gateway is the OAuth resource server. The IdP issues tokens. The MCP client presents them. Two flavors of token are possible:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Client credentials (M2M)&lt;/strong&gt; — the MCP client itself authenticates as a service account. One token, no user identity, no per-user scoping.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Authorization code + PKCE&lt;/strong&gt; — a real human signs in once via a browser redirect, the MCP client stores the resulting access + refresh token and presents the access token on every tool call.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;AWS&amp;rsquo;s blog walks through option 2. The interesting question is why you&amp;rsquo;d bother.&lt;/p&gt;
&lt;h2 id="when-client-credentials-is-enough"&gt;
&lt;a href="#when-client-credentials-is-enough" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
When client credentials is enough
&lt;/h2&gt;
&lt;p&gt;Be honest first. You don&amp;rsquo;t need auth code flow if all of these are true:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The tools operate on &lt;strong&gt;shared, non-user-scoped data&lt;/strong&gt; (a public knowledge base, an internal metrics aggregator, a read-only catalog).&lt;/li&gt;
&lt;li&gt;All callers are trusted (an internal agent platform, not a marketplace of third-party MCP clients).&lt;/li&gt;
&lt;li&gt;Audit trails don&amp;rsquo;t need to attribute calls to a specific human.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In that world, one client_id + client_secret per MCP client is fine. Rotate the secret, scope the tools, ship it.&lt;/p&gt;
&lt;h2 id="when-you-actually-need-auth-code-flow"&gt;
&lt;a href="#when-you-actually-need-auth-code-flow" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
When you actually need auth code flow
&lt;/h2&gt;
&lt;p&gt;The moment any of these show up, M2M falls apart:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Trigger&lt;/th&gt;
&lt;th&gt;Why M2M breaks&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Tools query &lt;strong&gt;per-user data&lt;/strong&gt; (this user&amp;rsquo;s Salesforce records, this user&amp;rsquo;s Jira tickets, this user&amp;rsquo;s S3 prefix)&lt;/td&gt;
&lt;td&gt;A shared service account either sees everything (over-privileged) or nothing (under-privileged). No middle ground without re-implementing auth inside the tool.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multi-tenant SaaS&lt;/strong&gt; integration&lt;/td&gt;
&lt;td&gt;The tool needs to call a downstream API as the user. You&amp;rsquo;d otherwise have to store and impersonate per-user credentials in the tool — exactly the thing OAuth was invented to avoid.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Audit/compliance&lt;/strong&gt; requires &amp;ldquo;who invoked this tool&amp;rdquo;&lt;/td&gt;
&lt;td&gt;The token&amp;rsquo;s &lt;code&gt;sub&lt;/code&gt; claim is your audit trail. M2M tokens have a service &lt;code&gt;sub&lt;/code&gt;, not a human one.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Consent screens&lt;/strong&gt; are a product requirement&lt;/td&gt;
&lt;td&gt;Only auth code flow shows the user &amp;ldquo;this MCP client wants to read your X&amp;rdquo; before tokens are minted.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;If you&amp;rsquo;re building a tool gateway for an internal agent that does platform work, M2M is fine. If you&amp;rsquo;re letting a user point Cursor at your gateway and have it act on their behalf, you need auth code flow. There is no middle option that doesn&amp;rsquo;t eventually become a homebrew auth system.&lt;/p&gt;
&lt;h2 id="the-three-things-that-surprised-me"&gt;
&lt;a href="#the-three-things-that-surprised-me" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The three things that surprised me
&lt;/h2&gt;
&lt;p&gt;The tutorial covers the happy path. These don&amp;rsquo;t get enough airtime.&lt;/p&gt;
&lt;h3 id="1-mcp-transports-and-browser-redirects-dont-naturally-mix"&gt;
&lt;a href="#1-mcp-transports-and-browser-redirects-dont-naturally-mix" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1. MCP transports and browser redirects don&amp;rsquo;t naturally mix
&lt;/h3&gt;
&lt;p&gt;OAuth auth code flow assumes a browser. MCP clients commonly run over &lt;strong&gt;stdio&lt;/strong&gt; (Cursor, Claude Desktop) or &lt;strong&gt;SSE&lt;/strong&gt;. Stdio has no browser. SSE has a browser but the MCP server doesn&amp;rsquo;t drive it.&lt;/p&gt;
&lt;p&gt;The MCP spec&amp;rsquo;s answer is: the &lt;strong&gt;client&lt;/strong&gt; opens a browser to the authorization endpoint when it doesn&amp;rsquo;t have a valid token, captures the redirect, exchanges the code, and stores the result. The gateway never touches a browser. This works, but means:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Every MCP client implements its own token cache and refresh logic. Cursor does it differently from Claude Desktop. Your custom client will too.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;redirect_uri&lt;/code&gt; of &lt;code&gt;http://localhost:&amp;lt;random-port&amp;gt;&lt;/code&gt; is the only realistic option for desktop clients. Register a wildcard or a generous range with your IdP, or expect to whitelist new ports forever.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="2-registration-is-provider-specific-not-automatic"&gt;
&lt;a href="#2-registration-is-provider-specific-not-automatic" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2. Registration is provider-specific, not automatic
&lt;/h3&gt;
&lt;p&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc7591"&gt;RFC 7591&lt;/a&gt; lets an MCP client self-register with the IdP at connection time. The final &lt;a href="https://blog.modelcontextprotocol.io/posts/2026-07-28/"&gt;2026-07-28 MCP specification&lt;/a&gt; formally deprecates Dynamic Client Registration in favor of Client ID Metadata Documents (CIMD), but that does not make every provider or AgentCore configuration dynamic. In the AWS Cognito walkthrough, you still pre-register the OAuth client; other identity providers and deployment paths may support different registration options.&lt;/p&gt;
&lt;p&gt;Practical consequence: verify the registration method supported by the exact IdP and AgentCore path. For Cognito, pre-create the client and configure its redirect URIs and scopes; do not promise universal dynamic registration to MCP users.&lt;/p&gt;
&lt;h3 id="3-token-lifetime--agent-session-lifetime"&gt;
&lt;a href="#3-token-lifetime--agent-session-lifetime" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
3. Token lifetime ≠ agent session lifetime
&lt;/h3&gt;
&lt;p&gt;A long-running agent session might span hours. Default access tokens are 60 minutes. Auth code flow gives you a refresh token, but:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Refresh in stdio MCP is non-trivial&lt;/strong&gt; — the client has to detect a 401 from the gateway, run the refresh, retry. Most stock MCP client libraries don&amp;rsquo;t do this for you yet.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Refresh tokens are bearer secrets&lt;/strong&gt;. Store them in OS keychain (Keychain on macOS, Credential Manager on Windows), not a JSON file in &lt;code&gt;~/.config/&lt;/code&gt;. Cursor and Claude Desktop do this; your custom client should too.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Idle expiration vs absolute expiration&lt;/strong&gt;: pick a refresh token policy that survives a 4-hour agent run but doesn&amp;rsquo;t live forever. 30-day absolute, 24-hour idle is a reasonable starting point.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="scope-design--the-part-everyone-underestimates"&gt;
&lt;a href="#scope-design--the-part-everyone-underestimates" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Scope design — the part everyone underestimates
&lt;/h2&gt;
&lt;p&gt;Two ways to design scopes:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Per-tool scopes&lt;/strong&gt;: &lt;code&gt;tool:read-account&lt;/code&gt;, &lt;code&gt;tool:create-opportunity&lt;/code&gt;, &lt;code&gt;tool:run-report&lt;/code&gt;. Fine-grained, audit-friendly, but the consent screen turns into a wall of checkboxes and you&amp;rsquo;ll regret naming the 47th scope.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Domain scopes&lt;/strong&gt;: &lt;code&gt;crm:read&lt;/code&gt;, &lt;code&gt;crm:write&lt;/code&gt;, &lt;code&gt;analytics:read&lt;/code&gt;. Coarser, the consent screen is human-readable, but a single scope grants a basket of tools. Lateral movement risk if any one tool is compromised.&lt;/p&gt;
&lt;p&gt;The right answer is usually &lt;strong&gt;domain scopes for the consent UX, plus tool-level authorization inside the gateway&lt;/strong&gt; (the access token unlocks the door, the gateway&amp;rsquo;s policy decides which Lambda you can actually invoke). That way users see five checkboxes, not fifty, and you still get fine-grained control.&lt;/p&gt;
&lt;h2 id="pitfalls-ive-seen--expect-to-see"&gt;
&lt;a href="#pitfalls-ive-seen--expect-to-see" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pitfalls I&amp;rsquo;ve seen / expect to see
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Mixing M2M and user clients in one Cognito resource server.&lt;/strong&gt; Works, but the scopes pollute each other and the consent screen shows M2M-only scopes to humans. Use two resource servers from day one.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Forgetting &lt;code&gt;redirect_uri&lt;/code&gt; for &lt;code&gt;http://localhost&lt;/code&gt; in non-prod.&lt;/strong&gt; Cognito, Okta, and Auth0 all let you register it; if you don&amp;rsquo;t, local dev breaks and the error message points at the wrong layer.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Trusting the IdP&amp;rsquo;s &lt;code&gt;aud&lt;/code&gt; claim default.&lt;/strong&gt; AgentCore Gateway expects a specific audience. Cognito&amp;rsquo;s default for user-pool tokens is the user pool&amp;rsquo;s app client ID, which is not what Gateway wants if you&amp;rsquo;ve set up an explicit resource server. Configure the audience explicitly on both sides.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Skipping PKCE because &amp;ldquo;we&amp;rsquo;re using a confidential client&amp;rdquo;.&lt;/strong&gt; Auth code flow without PKCE on a public client (every desktop MCP client is public) is a downgrade attack waiting to happen. PKCE is free; turn it on.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Logging access tokens in CloudWatch.&lt;/strong&gt; Gateway request logs at debug level can include the &lt;code&gt;Authorization&lt;/code&gt; header. Strip it before shipping logs anywhere durable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="a-decision-tree-in-one-paragraph"&gt;
&lt;a href="#a-decision-tree-in-one-paragraph" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A decision tree, in one paragraph
&lt;/h2&gt;
&lt;p&gt;If your tool reads or writes user-scoped data, or you&amp;rsquo;ll ever need to know which human invoked a call, use auth code flow with PKCE. Choose the IdP based on its current AgentCore integration and registration support, not a generic provider checklist. Design domain-level scopes for the consent screen, enforce tool-level policy inside Gateway, and pre-register clients where the provider requires it. Store refresh tokens in OS keychain. Set token lifetimes to match agent session expectations, not IdP defaults. Don&amp;rsquo;t put both M2M and user clients in the same resource server. Test the local dev redirect path before you test the prod one.&lt;/p&gt;
&lt;p&gt;The AWS tutorial is the right thing to follow once you&amp;rsquo;ve decided. The deciding is the part you have to do yourself.&lt;/p&gt;</description></item><item><title>Per-Request Cost Attribution on Amazon Bedrock — InvokeModel Metadata, Inference Profiles, and What to Use When</title><link>https://www.yopa.page/blog/2026-06-01-bedrock-request-level-usage-attribution.html</link><pubDate>Mon, 01 Jun 2026 02:30:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-06-01-bedrock-request-level-usage-attribution.html</guid><description>
&lt;p&gt;If you run more than one team, product, or environment on top of Amazon Bedrock, the very first finance question you&amp;rsquo;ll hear is: &lt;strong&gt;&amp;ldquo;who used the tokens?&amp;rdquo;&lt;/strong&gt; And until recently, the honest answer was &lt;em&gt;&amp;ldquo;it depends on which API you&amp;rsquo;re calling.&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;Converse&lt;/code&gt; and &lt;code&gt;ConverseStream&lt;/code&gt; APIs already supported a &lt;code&gt;requestMetadata&lt;/code&gt; field — arbitrary key/value tags that flow into your invocation logs. But a lot of production code still uses the older, lower-level &lt;code&gt;InvokeModel&lt;/code&gt; and &lt;code&gt;InvokeModelWithResponseStream&lt;/code&gt; APIs, and those didn&amp;rsquo;t accept metadata at all. You either had to migrate to &lt;code&gt;Converse&lt;/code&gt;, separate workloads behind different inference profiles, or live with IAM-role-level granularity.&lt;/p&gt;
&lt;p&gt;In May 2026, AWS &lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/05/amazon-bedrock-request-level-usage-attribution/"&gt;closed that gap&lt;/a&gt;: &lt;code&gt;InvokeModel&lt;/code&gt; and &lt;code&gt;InvokeModelWithResponseStream&lt;/code&gt; now accept request-level metadata too, available in every commercial region where Bedrock is offered.&lt;/p&gt;
&lt;p&gt;That sounds small. It isn&amp;rsquo;t. It changes which attribution strategy is the right default.&lt;/p&gt;
&lt;h2 id="three-ways-to-attribute-bedrock-spend"&gt;
&lt;a href="#three-ways-to-attribute-bedrock-spend" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Three ways to attribute Bedrock spend
&lt;/h2&gt;
&lt;p&gt;There are now three production-grade ways to slice &amp;ldquo;who used what&amp;rdquo; on Bedrock. They aren&amp;rsquo;t mutually exclusive — most mature setups use two of them at once — but they answer different questions.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;Granularity&lt;/th&gt;
&lt;th&gt;What it answers&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Application Inference Profile&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Per service / per app&lt;/td&gt;
&lt;td&gt;&amp;ldquo;How much does &lt;em&gt;this product&lt;/em&gt; cost to run?&amp;rdquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;IAM principal&lt;/strong&gt; (role/user)&lt;/td&gt;
&lt;td&gt;Per role&lt;/td&gt;
&lt;td&gt;&amp;ldquo;How much did &lt;em&gt;this caller&lt;/em&gt; spend?&amp;rdquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Request metadata&lt;/strong&gt; (new)&lt;/td&gt;
&lt;td&gt;Per request&lt;/td&gt;
&lt;td&gt;&amp;ldquo;How much did &lt;em&gt;this team / project / env / user&lt;/em&gt; spend, even on a shared profile?&amp;rdquo;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="application-inference-profiles"&gt;
&lt;a href="#application-inference-profiles" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Application inference profiles
&lt;/h3&gt;
&lt;p&gt;If you&amp;rsquo;ve already built a platform team, this is probably your current default. You provision an inference profile per service, route the service&amp;rsquo;s traffic through it, and Cost Explorer breaks the bill down by profile ARN. Clean, audit-friendly, and works for any Bedrock model that supports application inference profiles (notably the Claude family via cross-region inference).&lt;/p&gt;
&lt;p&gt;The catch: profiles are a coarse unit. If three internal teams share one &amp;ldquo;shared experimentation&amp;rdquo; service, they all show up under the same profile.&lt;/p&gt;
&lt;h3 id="iam-based-attribution"&gt;
&lt;a href="#iam-based-attribution" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
IAM-based attribution
&lt;/h3&gt;
&lt;p&gt;Free, automatic, and the lowest-effort tier. Cost Explorer can group spend by IAM principal if your callers each have distinct roles. Works the moment you turn it on, and pairs well with SSO. The downside is that it&amp;rsquo;s only as granular as your IAM design — and most teams converge on a small number of &amp;ldquo;service roles&amp;rdquo; that hide a lot of internal structure.&lt;/p&gt;
&lt;h3 id="request-metadata"&gt;
&lt;a href="#request-metadata" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Request metadata
&lt;/h3&gt;
&lt;p&gt;This is the new one. You attach a small map of tags — say &lt;code&gt;{ &amp;quot;team&amp;quot;: &amp;quot;growth&amp;quot;, &amp;quot;project&amp;quot;: &amp;quot;onboarding-assistant&amp;quot;, &amp;quot;env&amp;quot;: &amp;quot;prod&amp;quot;, &amp;quot;actor_group&amp;quot;: &amp;quot;beta&amp;quot; }&lt;/code&gt; — to each &lt;code&gt;InvokeModel&lt;/code&gt; call. The tags land in your model invocation log on S3 (or CloudWatch), and you query them with Athena, OpenSearch, or whatever you already use.&lt;/p&gt;
&lt;p&gt;The win is &lt;em&gt;post-hoc flexibility&lt;/em&gt;. You don&amp;rsquo;t have to redesign your IAM tree or split inference profiles to ask a new question. Tag every call with &lt;code&gt;team&lt;/code&gt; and &lt;code&gt;feature&lt;/code&gt;, and you can re-slice spend by either dimension forever after.&lt;/p&gt;
&lt;h2 id="when-each-one-wins"&gt;
&lt;a href="#when-each-one-wins" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
When each one wins
&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s the rule of thumb I&amp;rsquo;ve landed on after running this in production:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Inference profiles&lt;/strong&gt; are the right unit for &lt;em&gt;billing chargeback&lt;/em&gt; between teams that own separate products. They show up cleanly in AWS Cost Explorer, the bill is hard to game, and they give the platform team a real handle (rate limits, model access, regional routing).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;IAM&lt;/strong&gt; is the right unit for &lt;em&gt;security boundaries&lt;/em&gt;. If two roles can see each other&amp;rsquo;s data, no amount of metadata tagging fixes that. Use IAM to enforce isolation; don&amp;rsquo;t use it as your primary cost knob.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Request metadata&lt;/strong&gt; is the right unit for &lt;em&gt;product analytics and per-feature unit economics&lt;/em&gt;. &amp;ldquo;How much does the suggestion box cost per active user?&amp;rdquo; &amp;ldquo;Did the new prompt template raise our per-conversation token cost?&amp;rdquo; These are questions you&amp;rsquo;ll keep inventing, and you don&amp;rsquo;t want to file an infra ticket every time.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In a healthy setup all three coexist: profiles for the bill, IAM for the blast radius, metadata for the dashboard.&lt;/p&gt;
&lt;h2 id="wiring-it-up"&gt;
&lt;a href="#wiring-it-up" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Wiring it up
&lt;/h2&gt;
&lt;p&gt;The mechanics are simple, but there are three steps you can&amp;rsquo;t skip.&lt;/p&gt;
&lt;h3 id="1-turn-on-model-invocation-logging"&gt;
&lt;a href="#1-turn-on-model-invocation-logging" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1. Turn on model invocation logging
&lt;/h3&gt;
&lt;p&gt;Metadata only matters if you can read it back. Enable Bedrock model invocation logging in each region you call from, and pick a destination — S3 for cheap long-term storage and Athena queries, CloudWatch Logs if you want to grep in real time. Both is fine; you&amp;rsquo;ll usually settle on one.&lt;/p&gt;
&lt;p&gt;This is a per-region setting and it&amp;rsquo;s easy to forget when you spin up a new region. Put it in your account-bootstrap CDK / Terraform.&lt;/p&gt;
&lt;h3 id="2-pass-requestmetadata-on-every-call"&gt;
&lt;a href="#2-pass-requestmetadata-on-every-call" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2. Pass &lt;code&gt;requestMetadata&lt;/code&gt; on every call
&lt;/h3&gt;
&lt;p&gt;For &lt;code&gt;InvokeModel&lt;/code&gt; and &lt;code&gt;InvokeModelWithResponseStream&lt;/code&gt;, the new &lt;code&gt;requestMetadata&lt;/code&gt; parameter takes a flat string-to-string map. Keys and values are limited in length (think tag-style budgets, not blob storage), and the same field works on &lt;code&gt;Converse&lt;/code&gt; / &lt;code&gt;ConverseStream&lt;/code&gt; for code paths that already use those.&lt;/p&gt;
&lt;p&gt;The critical move is to &lt;strong&gt;inject metadata in your shared client wrapper&lt;/strong&gt;, not in each call site. If you let individual features set their own tags, you&amp;rsquo;ll end up with a soup of inconsistent keys (&lt;code&gt;team&lt;/code&gt;, &lt;code&gt;Team&lt;/code&gt;, &lt;code&gt;team_name&lt;/code&gt;, &lt;code&gt;owning_team&lt;/code&gt;) and your dashboards will lie. Pick a schema, write it down, enforce it in one place.&lt;/p&gt;
&lt;p&gt;A reasonable starter schema:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;th&gt;Required?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;team&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;growth&lt;/code&gt;, &lt;code&gt;core&lt;/code&gt;, &lt;code&gt;platform&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;service&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;onboarding-assistant&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;env&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;dev&lt;/code&gt;, &lt;code&gt;qa&lt;/code&gt;, &lt;code&gt;prod&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;feature&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;summarize&lt;/code&gt;, &lt;code&gt;chat&lt;/code&gt;, &lt;code&gt;eval&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;optional&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;request_id&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;req_01J…&lt;/code&gt; (your own correlation id)&lt;/td&gt;
&lt;td&gt;optional&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Resist the urge to put PII or full user IDs in there unless you&amp;rsquo;ve thought through retention. Logs live for a long time.&lt;/p&gt;
&lt;h3 id="3-query-the-logs"&gt;
&lt;a href="#3-query-the-logs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
3. Query the logs
&lt;/h3&gt;
&lt;p&gt;If you&amp;rsquo;re on S3, the &lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/model-invocation-logging.html"&gt;official partition layout&lt;/a&gt; plays well with Athena. A first query usually looks like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sql" data-lang="sql"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;SELECT&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;request_metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;team&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;team&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;request_metadata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;service&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input_token_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;input_tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;SUM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;output_token_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;output_tokens&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;bedrock_invocation_logs&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WHERE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;date&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;current_date&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;interval&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;7&amp;#39;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;day&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;GROUP&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ORDER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;BY&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;input_tokens&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;output_tokens&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Translate tokens to an estimate with the published per-model rates and you have a per-team dashboard, sliceable by any tag you set. Request metadata is log context, not a Cost Explorer/CUR cost-allocation tag; use application inference profiles or the supported AWS cost-management features when you need invoice-grade billed dollars.&lt;/p&gt;
&lt;h2 id="pitfalls-ive-actually-hit"&gt;
&lt;a href="#pitfalls-ive-actually-hit" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pitfalls I&amp;rsquo;ve actually hit
&lt;/h2&gt;
&lt;p&gt;A few things to save you a debug session:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Region mismatches.&lt;/strong&gt; Logging is per-region. Calls to a model in a region where logging is off are absent from your metadata dashboard even though the underlying Bedrock spend still exists in AWS billing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Streaming responses.&lt;/strong&gt; &lt;code&gt;InvokeModelWithResponseStream&lt;/code&gt; records token counts only after the stream completes. If your client disconnects early, the row may still be written but with partial data. Don&amp;rsquo;t assume every row is &amp;ldquo;clean.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Schema drift.&lt;/strong&gt; Once you ship a metadata schema, treat it like a public API. Renaming &lt;code&gt;team&lt;/code&gt; to &lt;code&gt;owning_team&lt;/code&gt; six months in will fork your dashboards. Add new keys, deprecate old ones, never silently rename.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t tag what IAM should enforce.&lt;/strong&gt; If &lt;code&gt;env=prod&lt;/code&gt; is a metadata tag, &lt;em&gt;anyone with InvokeModel permission&lt;/em&gt; can claim to be prod. Use IAM (and ideally separate roles per env) for anything that must not be forgeable.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="so-which-one-should-you-use"&gt;
&lt;a href="#so-which-one-should-you-use" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
So which one should you use?
&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re starting today on Bedrock:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Set up &lt;strong&gt;at least one inference profile per product&lt;/strong&gt; that has its own bill or SLO. This is your invoice-grade unit.&lt;/li&gt;
&lt;li&gt;Give each calling service a &lt;strong&gt;distinct IAM role&lt;/strong&gt;, even if it&amp;rsquo;s tedious. Future-you will thank present-you when the security review lands.&lt;/li&gt;
&lt;li&gt;Add &lt;strong&gt;request metadata in your shared client wrapper from day one&lt;/strong&gt;, with a small, enforced schema. It costs nothing extra at runtime and gives you a permanent escape hatch for the questions you haven&amp;rsquo;t thought of yet.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The new InvokeModel metadata feature isn&amp;rsquo;t going to change your architecture. But it does mean you no longer have to choose between &amp;ldquo;use the modern Converse API&amp;rdquo; and &amp;ldquo;have per-request attribution.&amp;rdquo; You can keep your existing InvokeModel-based code and still get the granularity that used to require a migration — which, in practice, is how most of these features actually get adopted.&lt;/p&gt;
&lt;h2 id="references"&gt;
&lt;a href="#references" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
References
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://aws.amazon.com/about-aws/whats-new/2026/05/amazon-bedrock-request-level-usage-attribution/"&gt;AWS What&amp;rsquo;s New — Bedrock request-level usage attribution (May 2026)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/model-invocation-logging.html"&gt;Bedrock model invocation logging&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/cost-management.html"&gt;Bedrock cost management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/inference-profiles.html"&gt;Application inference profiles&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Docker Build SSL Failures Behind a Corporate MITM Proxy: Two Patterns</title><link>https://www.yopa.page/blog/2026-05-30-docker-build-ssl-zscaler-cert.html</link><pubDate>Sat, 30 May 2026 12:30:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-05-30-docker-build-ssl-zscaler-cert.html</guid><description>
&lt;p&gt;If you work somewhere with Zscaler or Netskope, Cisco Umbrella, &amp;hellip; any &amp;ldquo;secure web gateway&amp;rdquo; that does TLS interception; you have probably seen this during a &lt;code&gt;docker build&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ERROR: Could not install packages due to an OSError:
HTTPSConnectionPool(host=&amp;#39;files.pythonhosted.org&amp;#39;, port=443):
Max retries exceeded with url: .../something-1.9.0-py3-none-any.whl.metadata
(Caused by SSLError(SSLCertVerificationError(1,
&amp;#39;[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed:
unable to get local issuer certificate (_ssl.c:1032)&amp;#39;)))
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Your host laptop is fine — macOS Keychain trusts the proxy&amp;rsquo;s root CA. The Docker container is not, because it&amp;rsquo;s a fresh Linux filesystem with only the public CA bundle. The proxy intercepts the TLS handshake to PyPI, presents its own cert, and the container has no idea who signed it.&lt;/p&gt;
&lt;p&gt;The fix is the same idea in both directions: &lt;strong&gt;get the proxy&amp;rsquo;s root CA into the container&amp;rsquo;s trust store before the package manager runs&lt;/strong&gt;. The how-to differs depending on whether you control the Dockerfile or not.&lt;/p&gt;
&lt;h2 id="pattern-1--you-control-the-dockerfile-custom-images-dockerimageasset"&gt;
&lt;a href="#pattern-1--you-control-the-dockerfile-custom-images-dockerimageasset" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pattern 1 — You control the Dockerfile (custom images, &lt;code&gt;DockerImageAsset&lt;/code&gt;)
&lt;/h2&gt;
&lt;p&gt;This is the easy case. Add an early &lt;code&gt;RUN&lt;/code&gt; block that takes a base64-encoded cert as a build arg and installs it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ARG&lt;/span&gt; ZSCALER_CERT_BASE64&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -n &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$ZSCALER_CERT_BASE64&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$ZSCALER_CERT_BASE64&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; base64 -d &amp;gt; /usr/local/share/ca-certificates/zscaler.crt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-ca-certificates&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Two things matter here:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Position&lt;/strong&gt;: this block must come &lt;em&gt;before&lt;/em&gt; any &lt;code&gt;pip install&lt;/code&gt;, &lt;code&gt;apt-get update&lt;/code&gt; (over HTTPS), &lt;code&gt;npm install&lt;/code&gt;, etc. Anything that hits the internet needs the trust store updated first.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;It&amp;rsquo;s gated on the build arg.&lt;/strong&gt; When the build arg is empty (CI/CD, where there is no proxy), the block is a no-op. You don&amp;rsquo;t want to ship a Zscaler cert to production.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;On the build orchestrator side (CDK in my case), pull the cert from Keychain at synth time:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;execSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;child_process&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getZscalerCertBase64&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;security find-certificate -a -c &amp;#34;Zscaler&amp;#34; -p /Library/Keychains/System.keychain&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;encoding&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;utf-8&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stdio&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;ignore&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;pipe&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ignore&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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;pem&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;pem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;No Zscaler cert in System Keychain&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pem&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;base64&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buildArgs&lt;/span&gt;: &lt;span class="kt"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;devMode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;buildArgs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ZSCALER_CERT_BASE64&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getZscalerCertBase64&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;DockerImageAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ImageAsset&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;directory&lt;/span&gt;: &lt;span class="kt"&gt;srcDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buildArgs&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;security find-certificate -a -c &amp;quot;Zscaler&amp;quot; -p&lt;/code&gt; returns one or more PEM blocks concatenated. Base64-encode the whole blob; on the container side, &lt;code&gt;update-ca-certificates&lt;/code&gt; happily handles a multi-cert PEM bundle.&lt;/p&gt;
&lt;p&gt;That&amp;rsquo;s it for images you own.&lt;/p&gt;
&lt;h2 id="pattern-2--you-dont-control-the-dockerfile-pythonfunction-pythonlayerversion"&gt;
&lt;a href="#pattern-2--you-dont-control-the-dockerfile-pythonfunction-pythonlayerversion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pattern 2 — You don&amp;rsquo;t control the Dockerfile (&lt;code&gt;PythonFunction&lt;/code&gt;, &lt;code&gt;PythonLayerVersion&lt;/code&gt;)
&lt;/h2&gt;
&lt;p&gt;This is the case that took me a few hours to figure out. AWS CDK&amp;rsquo;s &lt;code&gt;@aws-cdk/aws-lambda-python-alpha&lt;/code&gt; (&lt;code&gt;PythonFunction&lt;/code&gt;, &lt;code&gt;PythonLayerVersion&lt;/code&gt;) bundles your Lambda code by spinning up &lt;code&gt;public.ecr.aws/sam/build-python3.13&lt;/code&gt; (or whichever runtime) and running &lt;code&gt;pip install&lt;/code&gt; &lt;em&gt;inside that container&lt;/em&gt;. You don&amp;rsquo;t write that Dockerfile. So Pattern 1 doesn&amp;rsquo;t help.&lt;/p&gt;
&lt;p&gt;The fix: pre-bake a custom bundling image with the Zscaler cert already installed, push it to ECR, and tell CDK to use that image instead of the default SAM one.&lt;/p&gt;
&lt;h3 id="the-custom-bundling-dockerfile"&gt;
&lt;a href="#the-custom-bundling-dockerfile" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The custom bundling Dockerfile
&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ARG&lt;/span&gt; &lt;span class="nv"&gt;IMAGE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;public.ecr.aws/sam/build-python3.13&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;$IMAGE&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ARG&lt;/span&gt; &lt;span class="nv"&gt;ZSCALER_CERT_BASE64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;PIP_CACHE_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/pip-cache&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;POETRY_CACHE_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/poetry-cache&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;UV_CACHE_DIR&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/tmp/uv-cache&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/usr/app/venv/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;root&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -n &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$ZSCALER_CERT_BASE64&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$ZSCALER_CERT_BASE64&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; base64 -d &amp;gt; /etc/pki/ca-trust/source/anchors/zscaler.crt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-ca-trust extract&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;REQUESTS_CA_BUNDLE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/pki/tls/certs/ca-bundle.crt&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;SSL_CERT_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/pki/tls/certs/ca-bundle.crt&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;UV_NATIVE_TLS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; python -m venv /usr/app/venv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mkdir -p /tmp/pip-cache /tmp/poetry-cache /tmp/uv-cache &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; chmod -R &lt;span class="m"&gt;777&lt;/span&gt; /tmp/pip-cache /tmp/poetry-cache /tmp/uv-cache &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pip install --upgrade pip &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; pip install pipenv poetry uv &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; rm -rf /tmp/pip-cache/* /tmp/poetry-cache/* /tmp/uv-cache/*&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;nobody&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;CMD&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;python&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Build it once, push to ECR:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws ecr create-repository --repository-name python-bundling --region us-east-1 &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;ZSCALER_CERT_BASE64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;security find-certificate -a -c &lt;span class="s2"&gt;&amp;#34;Zscaler&amp;#34;&lt;/span&gt; -p /Library/Keychains/System.keychain &lt;span class="p"&gt;|&lt;/span&gt; base64&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker build &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --build-arg &lt;span class="nv"&gt;ZSCALER_CERT_BASE64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$ZSCALER_CERT_BASE64&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -t &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$ACCOUNT&lt;/span&gt;&lt;span class="s2"&gt;.dkr.ecr.us-east-1.amazonaws.com/python-bundling:3.13&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; scripts/build/python-bundling
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws ecr get-login-password --region us-east-1 &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; docker login --username AWS --password-stdin &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$ACCOUNT&lt;/span&gt;&lt;span class="s2"&gt;.dkr.ecr.us-east-1.amazonaws.com&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker push &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$ACCOUNT&lt;/span&gt;&lt;span class="s2"&gt;.dkr.ecr.us-east-1.amazonaws.com/python-bundling:3.13&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Then in CDK, swap the bundling image only in dev mode:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;DockerImage&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;aws-cdk-lib&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getBundlingImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;region&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;devMode&lt;/span&gt;: &lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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;devMode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// CI/CD uses the default SAM image
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;DockerImage&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromRegistry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;.dkr.ecr.&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;.amazonaws.com/python-bundling:3.13`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getBundlingImage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;devMode&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;PythonLayerVersion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Layer&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;: &lt;span class="kt"&gt;path.join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;..&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;layers&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;shared&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;compatibleRuntimes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PYTHON_3_13&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;bundling&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;PythonFunction&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Fn&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;: &lt;span class="kt"&gt;path.join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;__dirname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;..&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;functions&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;create_chat&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;runtime&lt;/span&gt;: &lt;span class="kt"&gt;Runtime.PYTHON_3_13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;...(&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;bundling&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Once the image is in ECR, every project in that account can reuse it. No more rebuilding per project.&lt;/p&gt;
&lt;h2 id="pitfalls-i-actually-hit"&gt;
&lt;a href="#pitfalls-i-actually-hit" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pitfalls I actually hit
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Wrong cert path for the base distro.&lt;/strong&gt; SAM build images are Amazon Linux. The trust store path is &lt;code&gt;/etc/pki/ca-trust/source/anchors/&lt;/code&gt; and the update command is &lt;code&gt;update-ca-trust extract&lt;/code&gt;. If you copy a Debian/Ubuntu snippet (&lt;code&gt;/usr/local/share/ca-certificates/&lt;/code&gt; + &lt;code&gt;update-ca-certificates&lt;/code&gt;), the cert is silently ignored and you keep getting the same SSL error wondering why your &amp;ldquo;fix&amp;rdquo; didn&amp;rsquo;t take.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;chmod 755&lt;/code&gt; on pip cache dirs.&lt;/strong&gt; CDK runs the bundling container as a non-root user (typically &lt;code&gt;503:20&lt;/code&gt;). With &lt;code&gt;chmod 755&lt;/code&gt; on &lt;code&gt;/tmp/pip-cache&lt;/code&gt;, the rsync step at the end of bundling fails with exit code 23 because the non-root user can&amp;rsquo;t write. &lt;code&gt;chmod 777&lt;/code&gt; on cache dirs is annoying but necessary inside throwaway bundling images.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;uv&lt;/code&gt; ignoring the system trust store.&lt;/strong&gt; &lt;code&gt;uv&lt;/code&gt; ships its own TLS implementation by default. Setting &lt;code&gt;UV_NATIVE_TLS=true&lt;/code&gt; makes it fall back to OpenSSL&amp;rsquo;s CA store, which is where &lt;code&gt;update-ca-trust&lt;/code&gt; puts your cert. Without this env var, your cert is in the right place and &lt;code&gt;uv&lt;/code&gt; still doesn&amp;rsquo;t see it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CI/CD doesn&amp;rsquo;t have the proxy.&lt;/strong&gt; Both patterns are gated on &lt;code&gt;devMode&lt;/code&gt;. CodePipeline, GitHub Actions runners, etc. talk to PyPI directly; injecting a stale Zscaler cert there is at best wasted bytes and at worst a footgun if the cert ever expires. Always make the injection conditional.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;security find-certificate&lt;/code&gt; is macOS-only.&lt;/strong&gt; Linux dev hosts behind the same proxy need a different extraction (&lt;code&gt;/etc/ssl/certs/...&lt;/code&gt; or whatever your IT pushes). The CDK code should detect host OS and either extract differently or skip injection entirely.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Multiple Zscaler certs in Keychain.&lt;/strong&gt; &lt;code&gt;security find-certificate -a -c &amp;quot;Zscaler&amp;quot; -p&lt;/code&gt; concatenates every match. That&amp;rsquo;s fine — &lt;code&gt;update-ca-trust&lt;/code&gt; and &lt;code&gt;update-ca-certificates&lt;/code&gt; both accept multi-cert PEM bundles. Don&amp;rsquo;t try to filter to &amp;ldquo;just one&amp;rdquo;.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-to-do"&gt;
&lt;a href="#what-to-do" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What to do
&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re hitting this for the first time and it&amp;rsquo;s a custom image you wrote, use Pattern 1 — five lines of Dockerfile and a build arg. If it&amp;rsquo;s &lt;code&gt;PythonFunction&lt;/code&gt;/&lt;code&gt;PythonLayerVersion&lt;/code&gt; (or any CDK construct that picks its own bundling image), bite the bullet, build the custom bundling image once, push to ECR, and gate it behind a &lt;code&gt;devMode&lt;/code&gt; check.&lt;/p&gt;
&lt;p&gt;The 10 minutes you spend setting up Pattern 2 saves you from the next three hours of &amp;ldquo;but I added the cert, why is &lt;code&gt;pip&lt;/code&gt; still failing?&amp;rdquo; — because &lt;code&gt;pip&lt;/code&gt; is running in a container you didn&amp;rsquo;t write, and you can&amp;rsquo;t add the cert without owning that container.&lt;/p&gt;</description></item><item><title>Bruno + AWS SigV4 with an SSO Profile (No More Pasting Access Keys)</title><link>https://www.yopa.page/blog/2026-05-28-bruno-aws-sigv4-sso-profile.html</link><pubDate>Thu, 28 May 2026 11:50:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-05-28-bruno-aws-sigv4-sso-profile.html</guid><description>
&lt;p&gt;If you&amp;rsquo;ve been hitting an IAM-protected API Gateway with Bruno (or Postman, or Insomnia) the old way, you know the dance: &lt;code&gt;aws configure export-credentials&lt;/code&gt;, copy three values, paste them into the auth panel, hit send, hit send again two hours later, swear, repeat. Everyone who works with SSO ends up with a TODO buried somewhere that says &amp;ldquo;automate this.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Turns out you don&amp;rsquo;t have to. Bruno&amp;rsquo;s AWS SigV4 auth has a &lt;code&gt;profileName&lt;/code&gt; field that reads directly from &lt;code&gt;~/.aws/config&lt;/code&gt; and &lt;code&gt;~/.aws/sso/cache/&lt;/code&gt;. Set it once, log in to SSO once a day, and Bruno does the rest.&lt;/p&gt;
&lt;h2 id="the-setup"&gt;
&lt;a href="#the-setup" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The setup
&lt;/h2&gt;
&lt;p&gt;Two files. &lt;code&gt;opencollection.yml&lt;/code&gt; at the collection root for the auth config, and an environment file per AWS account/profile.&lt;/p&gt;
&lt;h3 id="collection-level-auth--opencollectionyml"&gt;
&lt;a href="#collection-level-auth--opencollectionyml" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Collection-level auth — &lt;code&gt;opencollection.yml&lt;/code&gt;
&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;request&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;awsv4&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;accessKeyId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;secretAccessKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;sessionToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;service&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;execute-api&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;region&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{{aws_region}}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;profileName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;{{aws_profile}}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The three credential fields stay empty strings. When Bruno sees a non-empty &lt;code&gt;profileName&lt;/code&gt;, it ignores them and resolves credentials through the AWS SDK&amp;rsquo;s profile chain — which means SSO sessions, role assumptions, and credential_process all just work.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;service: execute-api&lt;/code&gt; is for API Gateway. Swap it for &lt;code&gt;lambda&lt;/code&gt;, &lt;code&gt;s3&lt;/code&gt;, &lt;code&gt;bedrock&lt;/code&gt;, whatever you&amp;rsquo;re hitting. &lt;code&gt;region&lt;/code&gt; and &lt;code&gt;profileName&lt;/code&gt; come from the environment.&lt;/p&gt;
&lt;h3 id="environment-files--environmentsdevyml"&gt;
&lt;a href="#environment-files--environmentsdevyml" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Environment files — &lt;code&gt;environments/dev.yml&lt;/code&gt;
&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;Dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;variables&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;aws_profile&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;gap-dev&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;aws_region&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;us-east-1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;api_base_url&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;https://abc123.execute-api.us-east-1.amazonaws.com/blue&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;One file per environment. Switching account is now a click in Bruno&amp;rsquo;s environment dropdown — no auth panel to touch.&lt;/p&gt;
&lt;h3 id="daily-login"&gt;
&lt;a href="#daily-login" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Daily login
&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws sso login --profile gap-dev
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s the only ritual. SSO tokens last 8–12 hours depending on your org&amp;rsquo;s config; once a day in the morning, done.&lt;/p&gt;
&lt;h2 id="why-this-is-worth-the-five-minutes"&gt;
&lt;a href="#why-this-is-worth-the-five-minutes" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why this is worth the five minutes
&lt;/h2&gt;
&lt;p&gt;A few things you stop doing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Pasting credentials into a collection file.&lt;/strong&gt; Even with gitignore, temp creds end up in chat, in a screenshot, in a &lt;code&gt;.env.local&lt;/code&gt; you forgot about.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Re-exporting every two hours.&lt;/strong&gt; SSO temp creds are short-lived. With a static paste, you&amp;rsquo;re refreshing constantly. With &lt;code&gt;profileName&lt;/code&gt;, the SDK refreshes for you.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mixing up environments.&lt;/strong&gt; The auth block is the same across dev/qa/prod — only the environment variable changes. There&amp;rsquo;s no profile-specific access key sitting in the wrong panel.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="pitfalls-ive-actually-hit"&gt;
&lt;a href="#pitfalls-ive-actually-hit" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pitfalls I&amp;rsquo;ve actually hit
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;SSO token expired.&lt;/strong&gt; Bruno will sign with a stale session and you&amp;rsquo;ll get a confusing 403. &lt;code&gt;aws sso login --profile &amp;lt;name&amp;gt;&lt;/code&gt; and retry. If you hit a lot of these, alias &lt;code&gt;aws sso login --profile &amp;lt;name&amp;gt;&lt;/code&gt; to something short.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Wrong profile name.&lt;/strong&gt; Bruno fails silently to &amp;ldquo;no credentials found&amp;rdquo; if &lt;code&gt;profileName&lt;/code&gt; doesn&amp;rsquo;t match anything in &lt;code&gt;~/.aws/config&lt;/code&gt;. Run &lt;code&gt;aws configure list-profiles&lt;/code&gt; to confirm the exact string.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mixing auth modes.&lt;/strong&gt; If you accidentally leave a real access key in &lt;code&gt;accessKeyId&lt;/code&gt;, Bruno may use it instead of the profile depending on version. Keep those fields strict empty strings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;credential_process&lt;/code&gt; profiles work too.&lt;/strong&gt; If your org uses &lt;code&gt;credential_process&lt;/code&gt; (corporate SSO wrapper, Granted, etc.), Bruno picks it up — same &lt;code&gt;profileName&lt;/code&gt; field, no extra config.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Region mismatch.&lt;/strong&gt; SigV4 signatures include the region. If your env file says &lt;code&gt;us-east-1&lt;/code&gt; but you&amp;rsquo;re calling a &lt;code&gt;us-west-2&lt;/code&gt; endpoint, the signature is invalid. Easy to miss when you copy a URL across environments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Collection-level vs request-level auth.&lt;/strong&gt; If a single request overrides &lt;code&gt;auth&lt;/code&gt; to something else, your collection-level SigV4 doesn&amp;rsquo;t apply. Check &lt;code&gt;auth: { type: inherit }&lt;/code&gt; or remove the override.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="sigv4-fields-bruno-supports"&gt;
&lt;a href="#sigv4-fields-bruno-supports" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
SigV4 fields Bruno supports
&lt;/h2&gt;
&lt;p&gt;For reference, when you open the AWS Sig auth panel in the UI:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Access Key ID&lt;/li&gt;
&lt;li&gt;Secret Access Key&lt;/li&gt;
&lt;li&gt;Session Token&lt;/li&gt;
&lt;li&gt;Service (e.g. &lt;code&gt;execute-api&lt;/code&gt;, &lt;code&gt;s3&lt;/code&gt;, &lt;code&gt;lambda&lt;/code&gt;, &lt;code&gt;bedrock&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Region (e.g. &lt;code&gt;us-east-1&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Profile Name&lt;/strong&gt; — the one that makes the other three obsolete&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If &lt;code&gt;profileName&lt;/code&gt; is set, the first three are ignored. That&amp;rsquo;s the whole trick.&lt;/p&gt;
&lt;h2 id="the-short-version"&gt;
&lt;a href="#the-short-version" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The short version
&lt;/h2&gt;
&lt;p&gt;Empty strings in the credential fields, &lt;code&gt;profileName: &amp;quot;{{aws_profile}}&amp;quot;&lt;/code&gt;, one env var per environment, &lt;code&gt;aws sso login&lt;/code&gt; in the morning. Stop pasting temp creds into your API client.&lt;/p&gt;</description></item><item><title>Adding llms.txt to a Hugo Blog with Output Formats</title><link>https://www.yopa.page/blog/2026-05-27-adding-llms-txt-to-hugo.html</link><pubDate>Wed, 27 May 2026 03:00:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-05-27-adding-llms-txt-to-hugo.html</guid><description>
&lt;p&gt;In September 2024, &lt;a href="https://jeremy.fast.ai/"&gt;Jeremy Howard&lt;/a&gt; (Answer.AI / fast.ai) proposed &lt;a href="https://llmstxt.org/"&gt;&lt;code&gt;/llms.txt&lt;/code&gt;&lt;/a&gt; — a curated, plain-Markdown index that sits at a site&amp;rsquo;s root, alongside &lt;code&gt;robots.txt&lt;/code&gt; and &lt;code&gt;sitemap.xml&lt;/code&gt;. The pitch: LLM context windows are tiny, HTML is noisy, and crawlers shouldn&amp;rsquo;t have to parse 180 HTML pages to understand what your site is about. So give them a hand-curated map.&lt;/p&gt;
&lt;p&gt;Sounds reasonable. The harder question is whether it does anything yet.&lt;/p&gt;
&lt;h2 id="what-llmstxt-actually-is"&gt;
&lt;a href="#what-llmstxt-actually-is" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What &lt;code&gt;llms.txt&lt;/code&gt; actually is
&lt;/h2&gt;
&lt;p&gt;It&amp;rsquo;s a Markdown file with a strict, parseable structure:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# Site Name ← H1, exactly one, required
&amp;gt; One-line summary ← blockquote
Free-form prose (optional)
## Docs ← H2 sections
- [Title](URL): short description
## Optional ← reserved meaning: skip if context is tight
- [Appendix](URL)
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Two adjacent conventions show up in the spec:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;llms-full.txt&lt;/code&gt;&lt;/strong&gt; — same idea, but with all the linked content inlined into a single file. Useful for &amp;ldquo;drop the entire docs into a context window.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;.md&lt;/code&gt; mirror URLs&lt;/strong&gt; — every page at &lt;code&gt;/foo/&lt;/code&gt; is also reachable at &lt;code&gt;/foo.md&lt;/code&gt; as clean Markdown. nbdev does this automatically; FastHTML pioneered the pattern.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="adoption-docs-sites-yes-llm-providers-not-really"&gt;
&lt;a href="#adoption-docs-sites-yes-llm-providers-not-really" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Adoption: docs sites yes, LLM providers… not really
&lt;/h2&gt;
&lt;p&gt;Documentation sites have adopted it widely. Anthropic&amp;rsquo;s docs, Cloudflare, Mintlify (auto-generated), Vercel, Supabase, Hugging Face, FastHTML — all publish &lt;code&gt;/llms.txt&lt;/code&gt;. Mintlify, Fern, and Docusaurus plugins are doing most of the heavy lifting in spreading it.&lt;/p&gt;
&lt;p&gt;The part nobody likes to say out loud: &lt;strong&gt;none of the major LLM providers have publicly committed to fetching it.&lt;/strong&gt; OpenAI&amp;rsquo;s GPTBot, Anthropic&amp;rsquo;s ClaudeBot, Google-Extended — they all crawl HTML and follow &lt;code&gt;sitemap.xml&lt;/code&gt;. None of their published docs say &lt;code&gt;llms.txt&lt;/code&gt; is preferred or even read. John Mueller (Google) commented in late 2024 that there was no evidence any AI service was using it.&lt;/p&gt;
&lt;p&gt;So today, &lt;code&gt;llms.txt&lt;/code&gt; is closer to &lt;strong&gt;good documentation hygiene&lt;/strong&gt; than a working SEO channel. The cost of adding one is near-zero, though, and if/when providers start consuming it, you&amp;rsquo;re already there.&lt;/p&gt;
&lt;h2 id="implementation-in-hugo"&gt;
&lt;a href="#implementation-in-hugo" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Implementation in Hugo
&lt;/h2&gt;
&lt;p&gt;Two ways to add it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Drop a static &lt;code&gt;static/llms.txt&lt;/code&gt; file. Done. Stale within a month.&lt;/li&gt;
&lt;li&gt;Generate it from your content via Hugo&amp;rsquo;s &lt;a href="https://gohugo.io/templates/output-formats/"&gt;Output Formats&lt;/a&gt; so it stays current with every post.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;(2) is the only correct answer for a blog where posts are added regularly.&lt;/p&gt;
&lt;h3 id="step-1--register-the-output-format"&gt;
&lt;a href="#step-1--register-the-output-format" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 1 — register the output format
&lt;/h3&gt;
&lt;p&gt;In &lt;code&gt;config.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;outputFormats&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;LLMS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;mediaType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;text/plain&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;baseName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;llms&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;isPlainText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;notAlternative&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;outputs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;home&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="s2"&gt;&amp;#34;HTML&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;RSS&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;JSON&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;LLMS&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;isPlainText: true&lt;/code&gt; skips Hugo&amp;rsquo;s HTML-aware processing. &lt;code&gt;notAlternative: true&lt;/code&gt; keeps it out of the &lt;code&gt;&amp;lt;link rel=&amp;quot;alternate&amp;quot;&amp;gt;&lt;/code&gt; chain — &lt;code&gt;llms.txt&lt;/code&gt; is not an alternate representation of the homepage.&lt;/p&gt;
&lt;h3 id="step-2--write-the-template"&gt;
&lt;a href="#step-2--write-the-template" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 2 — write the template
&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;layouts/index.llms.txt&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go-html-template" data-lang="go-html-template"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;# &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.Site.Title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;gt; &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.Site.Params.siteDesc&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.Site.Params.siteName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt; is the personal tech blog of &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.Site.Params.author&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;## Pages
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- [About](&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;about.html&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;absURL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;): About the author
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- [Articles](&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;articles.html&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;absURL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;): Curated articles index
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;## Blog Posts
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;{{-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;range&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;first&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;80&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.Site.RegularPages&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;Section&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;blog&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- [&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.Title&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;](&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.Permalink&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;)&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="na"&gt;.Description&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;default&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="na"&gt;.Summary&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;plainify&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;truncate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;140&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;: &lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="na"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;{{-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;end&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;## Optional
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- [RSS Feed](&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;index.xml&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;absURL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- [Tags](&lt;span class="cp"&gt;{{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;tags/&amp;#34;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;absURL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="cp"&gt;}}&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Build, and Hugo emits &lt;code&gt;public/llms.txt&lt;/code&gt; automatically. Sync to S3 / CloudFront like any other static file. Verify the &lt;code&gt;Content-Type&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;hugo --gc --minify
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;head public/llms.txt &lt;span class="c1"&gt;# starts with &amp;#39;# thats.nono&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl -I https://www.yopa.page/llms.txt &lt;span class="c1"&gt;# Content-Type: text/plain&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s it.&lt;/p&gt;
&lt;h2 id="what-i-didnt-add-yet"&gt;
&lt;a href="#what-i-didnt-add-yet" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What I didn&amp;rsquo;t add (yet)
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;llms-full.txt&lt;/code&gt;&lt;/strong&gt;. Tempting, but for a blog with 180+ posts it&amp;rsquo;d be massive, and there&amp;rsquo;s no evidence anyone is fetching it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;.md&lt;/code&gt; mirror URLs.&lt;/strong&gt; Two extra lines in &lt;code&gt;outputs.page&lt;/code&gt; would do it, but it doubles the surface area for caching headaches and content drift. Defer.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="honest-take"&gt;
&lt;a href="#honest-take" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Honest take
&lt;/h2&gt;
&lt;p&gt;I added &lt;code&gt;llms.txt&lt;/code&gt; to yopa.page because the cost is one template and zero ongoing maintenance, not because I expect Claude or ChatGPT to start reading it tomorrow. If the standard catches on, my site is ready. If it doesn&amp;rsquo;t, I lost 30 minutes but well?&lt;/p&gt;
&lt;p&gt;That asymmetry — cheap to adopt, free option on future upside — is the whole reason to do this now. Don&amp;rsquo;t rewrite your content strategy around it. Don&amp;rsquo;t tell your boss it&amp;rsquo;ll move SEO numbers. Just ship the file and move on.&lt;/p&gt;
&lt;h2 id="references"&gt;
&lt;a href="#references" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
References
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Spec: &lt;a href="https://llmstxt.org/"&gt;https://llmstxt.org/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Repo: &lt;a href="https://github.com/AnswerDotAI/llms-txt"&gt;https://github.com/AnswerDotAI/llms-txt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;This blog&amp;rsquo;s &lt;a href="https://www.yopa.page/llms.txt"&gt;&lt;code&gt;llms.txt&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;PR that added it: &lt;a href="https://github.com/ypark9/yopa.page/pull/19"&gt;ypark9/yopa.page#19&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Deploy Your Own AI Agent to AWS — Hermes on ECS Fargate with Slack Integration</title><link>https://www.yopa.page/blog/2026-05-23-deploy-hermes-ai-agent-aws-ecs-fargate-slack.html</link><pubDate>Sat, 23 May 2026 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-05-23-deploy-hermes-ai-agent-aws-ecs-fargate-slack.html</guid><description>
&lt;blockquote&gt;
&lt;p&gt;A May 2026 field guide to running your own AI agent on AWS. The infrastructure and gotchas are real, but the upstream image and configuration must be re-checked before a new deployment.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://github.com/NousResearch/hermes-agent"&gt;Hermes Agent (NousResearch)&lt;/a&gt; |
&lt;a href="https://hermes-agent.nousresearch.com/docs/user-guide/docker/"&gt;Hermes Docker deployment&lt;/a&gt; |
&lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/home.html"&gt;AWS CDK Documentation&lt;/a&gt; |
&lt;a href="https://api.slack.com/apis/socket-mode"&gt;Slack Socket Mode&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Review note (2026-08-28):&lt;/strong&gt; This article records a deployment from May 23, 2026. Hermes now documents a supported Docker path using the &lt;code&gt;nousresearch/hermes-agent&lt;/code&gt; image and persistent data mounted at &lt;code&gt;/opt/data&lt;/code&gt;; schema migrations can create backups. The ECS/CDK architecture below is illustrative for this snapshot. Inspect the current &lt;a href="https://github.com/NousResearch/hermes-agent/releases"&gt;release page&lt;/a&gt; and &lt;a href="https://hermes-agent.nousresearch.com/docs/user-guide/configuration"&gt;configuration reference&lt;/a&gt; before copying it, and never overwrite a mounted config or secret store blindly.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Most AI agent tutorials end at &amp;ldquo;run it locally.&amp;rdquo; That&amp;rsquo;s fine for demos, but useless for teams. You want an always-on assistant that your team can DM on Slack, that remembers past conversations, that can read your documentation and use external tools — and that doesn&amp;rsquo;t die when your laptop goes to sleep.&lt;/p&gt;
&lt;p&gt;This post documents how I deployed &lt;a href="https://github.com/NousResearch/hermes-agent"&gt;Hermes Agent&lt;/a&gt; — an open-source AI agent framework by NousResearch — to AWS ECS Fargate, connected it to Slack via Socket Mode, backed it with Claude on Bedrock, and gave it persistent memory on EFS with a knowledge base on S3. The code blocks are copy-paste-able for that May snapshot only; validate image tags, configuration keys, and mount paths against the current upstream docs. Every gotcha is one I actually hit.&lt;/p&gt;
&lt;h2 id="what-youll-build"&gt;
&lt;a href="#what-youll-build" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What You&amp;rsquo;ll Build
&lt;/h2&gt;
&lt;p&gt;A single Hermes Agent running on ECS Fargate that:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Connects to your Slack workspace via Socket Mode (DMs and @mentions)&lt;/li&gt;
&lt;li&gt;Uses Claude (Sonnet or Opus) via Amazon Bedrock for inference&lt;/li&gt;
&lt;li&gt;Persists memory and learned skills on EFS across restarts&lt;/li&gt;
&lt;li&gt;Syncs a knowledge base from S3 every 15 minutes (no redeploy needed for updates)&lt;/li&gt;
&lt;li&gt;Supports MCP servers for external tool integration (Jira, GitHub, etc.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Estimated monthly cost:&lt;/strong&gt; ~$55–65 (excluding Bedrock inference, which varies by usage).&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;
&lt;a href="#prerequisites" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Prerequisites
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;AWS account with Bedrock model access enabled (Claude models in us-east-1)&lt;/li&gt;
&lt;li&gt;AWS CLI v2 configured with credentials&lt;/li&gt;
&lt;li&gt;Node.js 18+ and npm (for CDK)&lt;/li&gt;
&lt;li&gt;Docker Desktop&lt;/li&gt;
&lt;li&gt;A Slack workspace where you can install apps&lt;/li&gt;
&lt;li&gt;Basic familiarity with CDK, Docker, and ECS concepts&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; Enable Bedrock model access first. Go to AWS Console → Bedrock → Model access → Request access to Anthropic Claude models. This can take minutes to hours.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="architecture-overview"&gt;
&lt;a href="#architecture-overview" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Architecture Overview
&lt;/h2&gt;
&lt;p&gt;&lt;img
src="./images/Hermes%20Agent%20-%20AWS%20Architecture.png"
alt="Image not found: Hermes Agent - AWS Architecture"
style="
background: #f0f0f0;
color: #666;
padding: 20px;
text-align: center;
border: 1px solid #ddd;
display: block;
"
/&gt;&lt;/p&gt;
&lt;h3 id="why-this-architecture"&gt;
&lt;a href="#why-this-architecture" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why This Architecture?
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Socket Mode eliminates 80% of networking complexity.&lt;/strong&gt; Traditional Slack bots need an inbound HTTP endpoint — meaning a load balancer, public IP, TLS certificate, and DNS. Socket Mode flips this: the container initiates an outbound WebSocket to Slack. No ALB ($16/month saved), no public IP, no cert management. The container sits in a private subnet and is unreachable from the internet.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;EFS persists agent state across deployments.&lt;/strong&gt; Hermes stores conversation memory in SQLite, learned skills as files, and user profiles on disk. Without EFS, every deployment wipes this state. With EFS, the agent remembers everything even after a fresh container starts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;S3 decouples knowledge updates from container restarts.&lt;/strong&gt; Upload new docs to S3, and the agent picks them up within 15 minutes. No Docker build, no ECR push, no deployment. This is the key insight that makes the system operationally pleasant.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Private subnet + NAT Gateway&lt;/strong&gt; gives an outbound-only security posture. The container can reach Slack, Bedrock, and S3 — but nothing on the internet can reach the container.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="step-1-create-the-slack-app"&gt;
&lt;a href="#step-1-create-the-slack-app" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 1: Create the Slack App
&lt;/h2&gt;
&lt;p&gt;Before we touch AWS, let&amp;rsquo;s create the Slack app and collect our tokens.&lt;/p&gt;
&lt;h3 id="11-create-the-app"&gt;
&lt;a href="#11-create-the-app" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1.1 Create the App
&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="https://api.slack.com/apps"&gt;api.slack.com/apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;&amp;ldquo;Create New App&amp;rdquo;&lt;/strong&gt; → &lt;strong&gt;&amp;ldquo;From scratch&amp;rdquo;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Name it (e.g., &amp;ldquo;Hermes&amp;rdquo;), select your workspace&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="12-enable-socket-mode"&gt;
&lt;a href="#12-enable-socket-mode" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1.2 Enable Socket Mode
&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;Settings → Socket Mode&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Toggle &lt;strong&gt;Enable Socket Mode&lt;/strong&gt; to ON&lt;/li&gt;
&lt;li&gt;When prompted, create an app-level token:
&lt;ul&gt;
&lt;li&gt;Name it &lt;code&gt;socket-mode&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Add the &lt;code&gt;connections:write&lt;/code&gt; scope&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;Generate&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Save this token&lt;/strong&gt; — it starts with &lt;code&gt;xapp-&lt;/code&gt; (this is your &lt;code&gt;SLACK_APP_TOKEN&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="13-add-bot-token-scopes"&gt;
&lt;a href="#13-add-bot-token-scopes" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1.3 Add Bot Token Scopes
&lt;/h3&gt;
&lt;p&gt;Go to &lt;strong&gt;OAuth &amp;amp; Permissions → Scopes → Bot Token Scopes&lt;/strong&gt; and add:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scope&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;chat:write&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Send messages&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;im:write&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Open DMs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;im:read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Read DM conversations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;im:history&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Access DM message history&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;app_mentions:read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Respond to @mentions in channels&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;channels:read&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;List public channels&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="14-enable-the-messages-tab-dms"&gt;
&lt;a href="#14-enable-the-messages-tab-dms" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1.4 Enable the Messages Tab (DMs)
&lt;/h3&gt;
&lt;p&gt;Go to &lt;strong&gt;App Home&lt;/strong&gt; and check &lt;strong&gt;&amp;ldquo;Allow users to send Slash commands and messages from the messages tab&amp;rdquo;&lt;/strong&gt;. Without this, users cannot DM your bot — the message input won&amp;rsquo;t appear.&lt;/p&gt;
&lt;h3 id="15-subscribe-to-events"&gt;
&lt;a href="#15-subscribe-to-events" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1.5 Subscribe to Events
&lt;/h3&gt;
&lt;p&gt;Go to &lt;strong&gt;Event Subscriptions&lt;/strong&gt; → Toggle ON → Under &lt;strong&gt;Subscribe to bot events&lt;/strong&gt;, add:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;message.im&lt;/code&gt; — Receive DM messages&lt;/li&gt;
&lt;li&gt;&lt;code&gt;app_mention&lt;/code&gt; — Respond to @mentions in channels&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;This step is critical.&lt;/strong&gt; If you skip it, your bot&amp;rsquo;s Socket Mode connection will succeed (you&amp;rsquo;ll see logs), but no messages will ever arrive. The gateway runs, the container is healthy, yet the bot is deaf.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="16-install-to-workspace"&gt;
&lt;a href="#16-install-to-workspace" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1.6 Install to Workspace
&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Go to &lt;strong&gt;OAuth &amp;amp; Permissions&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;&amp;ldquo;Install to Workspace&amp;rdquo;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;Authorize the requested permissions&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Save the Bot User OAuth Token&lt;/strong&gt; — starts with &lt;code&gt;xoxb-&lt;/code&gt; (this is your &lt;code&gt;SLACK_BOT_TOKEN&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Any time you change scopes or event subscriptions&lt;/strong&gt;, you must reinstall the app. Slack won&amp;rsquo;t apply changes until you do.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 id="17-test-the-app-exists"&gt;
&lt;a href="#17-test-the-app-exists" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1.7 Test the App Exists
&lt;/h3&gt;
&lt;p&gt;Open Slack, go to Apps, find your bot. Send it &amp;ldquo;hello&amp;rdquo; — it won&amp;rsquo;t respond yet (no backend), but you should see it in the app list.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Keep both tokens safe.&lt;/strong&gt; We&amp;rsquo;ll store them in AWS Secrets Manager next.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="step-2-aws-infrastructure-with-cdk"&gt;
&lt;a href="#step-2-aws-infrastructure-with-cdk" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 2: AWS Infrastructure with CDK
&lt;/h2&gt;
&lt;p&gt;We&amp;rsquo;ll use CDK (TypeScript) to create all AWS resources. The complete stack creates: VPC networking, EFS, S3, Secrets Manager, ECS Cluster, Fargate Service, and all IAM permissions.&lt;/p&gt;
&lt;h3 id="21-project-setup"&gt;
&lt;a href="#21-project-setup" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2.1 Project Setup
&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir hermes-infra &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; hermes-infra
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npx cdk init app --language typescript
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm install aws-cdk-lib constructs
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="22-the-stack"&gt;
&lt;a href="#22-the-stack" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2.2 The Stack
&lt;/h3&gt;
&lt;p&gt;Replace &lt;code&gt;lib/hermes-infra-stack.ts&lt;/code&gt; with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt; 10
&lt;/span&gt;&lt;span class="lnt"&gt; 11
&lt;/span&gt;&lt;span class="lnt"&gt; 12
&lt;/span&gt;&lt;span class="lnt"&gt; 13
&lt;/span&gt;&lt;span class="lnt"&gt; 14
&lt;/span&gt;&lt;span class="lnt"&gt; 15
&lt;/span&gt;&lt;span class="lnt"&gt; 16
&lt;/span&gt;&lt;span class="lnt"&gt; 17
&lt;/span&gt;&lt;span class="lnt"&gt; 18
&lt;/span&gt;&lt;span class="lnt"&gt; 19
&lt;/span&gt;&lt;span class="lnt"&gt; 20
&lt;/span&gt;&lt;span class="lnt"&gt; 21
&lt;/span&gt;&lt;span class="lnt"&gt; 22
&lt;/span&gt;&lt;span class="lnt"&gt; 23
&lt;/span&gt;&lt;span class="lnt"&gt; 24
&lt;/span&gt;&lt;span class="lnt"&gt; 25
&lt;/span&gt;&lt;span class="lnt"&gt; 26
&lt;/span&gt;&lt;span class="lnt"&gt; 27
&lt;/span&gt;&lt;span class="lnt"&gt; 28
&lt;/span&gt;&lt;span class="lnt"&gt; 29
&lt;/span&gt;&lt;span class="lnt"&gt; 30
&lt;/span&gt;&lt;span class="lnt"&gt; 31
&lt;/span&gt;&lt;span class="lnt"&gt; 32
&lt;/span&gt;&lt;span class="lnt"&gt; 33
&lt;/span&gt;&lt;span class="lnt"&gt; 34
&lt;/span&gt;&lt;span class="lnt"&gt; 35
&lt;/span&gt;&lt;span class="lnt"&gt; 36
&lt;/span&gt;&lt;span class="lnt"&gt; 37
&lt;/span&gt;&lt;span class="lnt"&gt; 38
&lt;/span&gt;&lt;span class="lnt"&gt; 39
&lt;/span&gt;&lt;span class="lnt"&gt; 40
&lt;/span&gt;&lt;span class="lnt"&gt; 41
&lt;/span&gt;&lt;span class="lnt"&gt; 42
&lt;/span&gt;&lt;span class="lnt"&gt; 43
&lt;/span&gt;&lt;span class="lnt"&gt; 44
&lt;/span&gt;&lt;span class="lnt"&gt; 45
&lt;/span&gt;&lt;span class="lnt"&gt; 46
&lt;/span&gt;&lt;span class="lnt"&gt; 47
&lt;/span&gt;&lt;span class="lnt"&gt; 48
&lt;/span&gt;&lt;span class="lnt"&gt; 49
&lt;/span&gt;&lt;span class="lnt"&gt; 50
&lt;/span&gt;&lt;span class="lnt"&gt; 51
&lt;/span&gt;&lt;span class="lnt"&gt; 52
&lt;/span&gt;&lt;span class="lnt"&gt; 53
&lt;/span&gt;&lt;span class="lnt"&gt; 54
&lt;/span&gt;&lt;span class="lnt"&gt; 55
&lt;/span&gt;&lt;span class="lnt"&gt; 56
&lt;/span&gt;&lt;span class="lnt"&gt; 57
&lt;/span&gt;&lt;span class="lnt"&gt; 58
&lt;/span&gt;&lt;span class="lnt"&gt; 59
&lt;/span&gt;&lt;span class="lnt"&gt; 60
&lt;/span&gt;&lt;span class="lnt"&gt; 61
&lt;/span&gt;&lt;span class="lnt"&gt; 62
&lt;/span&gt;&lt;span class="lnt"&gt; 63
&lt;/span&gt;&lt;span class="lnt"&gt; 64
&lt;/span&gt;&lt;span class="lnt"&gt; 65
&lt;/span&gt;&lt;span class="lnt"&gt; 66
&lt;/span&gt;&lt;span class="lnt"&gt; 67
&lt;/span&gt;&lt;span class="lnt"&gt; 68
&lt;/span&gt;&lt;span class="lnt"&gt; 69
&lt;/span&gt;&lt;span class="lnt"&gt; 70
&lt;/span&gt;&lt;span class="lnt"&gt; 71
&lt;/span&gt;&lt;span class="lnt"&gt; 72
&lt;/span&gt;&lt;span class="lnt"&gt; 73
&lt;/span&gt;&lt;span class="lnt"&gt; 74
&lt;/span&gt;&lt;span class="lnt"&gt; 75
&lt;/span&gt;&lt;span class="lnt"&gt; 76
&lt;/span&gt;&lt;span class="lnt"&gt; 77
&lt;/span&gt;&lt;span class="lnt"&gt; 78
&lt;/span&gt;&lt;span class="lnt"&gt; 79
&lt;/span&gt;&lt;span class="lnt"&gt; 80
&lt;/span&gt;&lt;span class="lnt"&gt; 81
&lt;/span&gt;&lt;span class="lnt"&gt; 82
&lt;/span&gt;&lt;span class="lnt"&gt; 83
&lt;/span&gt;&lt;span class="lnt"&gt; 84
&lt;/span&gt;&lt;span class="lnt"&gt; 85
&lt;/span&gt;&lt;span class="lnt"&gt; 86
&lt;/span&gt;&lt;span class="lnt"&gt; 87
&lt;/span&gt;&lt;span class="lnt"&gt; 88
&lt;/span&gt;&lt;span class="lnt"&gt; 89
&lt;/span&gt;&lt;span class="lnt"&gt; 90
&lt;/span&gt;&lt;span class="lnt"&gt; 91
&lt;/span&gt;&lt;span class="lnt"&gt; 92
&lt;/span&gt;&lt;span class="lnt"&gt; 93
&lt;/span&gt;&lt;span class="lnt"&gt; 94
&lt;/span&gt;&lt;span class="lnt"&gt; 95
&lt;/span&gt;&lt;span class="lnt"&gt; 96
&lt;/span&gt;&lt;span class="lnt"&gt; 97
&lt;/span&gt;&lt;span class="lnt"&gt; 98
&lt;/span&gt;&lt;span class="lnt"&gt; 99
&lt;/span&gt;&lt;span class="lnt"&gt;100
&lt;/span&gt;&lt;span class="lnt"&gt;101
&lt;/span&gt;&lt;span class="lnt"&gt;102
&lt;/span&gt;&lt;span class="lnt"&gt;103
&lt;/span&gt;&lt;span class="lnt"&gt;104
&lt;/span&gt;&lt;span class="lnt"&gt;105
&lt;/span&gt;&lt;span class="lnt"&gt;106
&lt;/span&gt;&lt;span class="lnt"&gt;107
&lt;/span&gt;&lt;span class="lnt"&gt;108
&lt;/span&gt;&lt;span class="lnt"&gt;109
&lt;/span&gt;&lt;span class="lnt"&gt;110
&lt;/span&gt;&lt;span class="lnt"&gt;111
&lt;/span&gt;&lt;span class="lnt"&gt;112
&lt;/span&gt;&lt;span class="lnt"&gt;113
&lt;/span&gt;&lt;span class="lnt"&gt;114
&lt;/span&gt;&lt;span class="lnt"&gt;115
&lt;/span&gt;&lt;span class="lnt"&gt;116
&lt;/span&gt;&lt;span class="lnt"&gt;117
&lt;/span&gt;&lt;span class="lnt"&gt;118
&lt;/span&gt;&lt;span class="lnt"&gt;119
&lt;/span&gt;&lt;span class="lnt"&gt;120
&lt;/span&gt;&lt;span class="lnt"&gt;121
&lt;/span&gt;&lt;span class="lnt"&gt;122
&lt;/span&gt;&lt;span class="lnt"&gt;123
&lt;/span&gt;&lt;span class="lnt"&gt;124
&lt;/span&gt;&lt;span class="lnt"&gt;125
&lt;/span&gt;&lt;span class="lnt"&gt;126
&lt;/span&gt;&lt;span class="lnt"&gt;127
&lt;/span&gt;&lt;span class="lnt"&gt;128
&lt;/span&gt;&lt;span class="lnt"&gt;129
&lt;/span&gt;&lt;span class="lnt"&gt;130
&lt;/span&gt;&lt;span class="lnt"&gt;131
&lt;/span&gt;&lt;span class="lnt"&gt;132
&lt;/span&gt;&lt;span class="lnt"&gt;133
&lt;/span&gt;&lt;span class="lnt"&gt;134
&lt;/span&gt;&lt;span class="lnt"&gt;135
&lt;/span&gt;&lt;span class="lnt"&gt;136
&lt;/span&gt;&lt;span class="lnt"&gt;137
&lt;/span&gt;&lt;span class="lnt"&gt;138
&lt;/span&gt;&lt;span class="lnt"&gt;139
&lt;/span&gt;&lt;span class="lnt"&gt;140
&lt;/span&gt;&lt;span class="lnt"&gt;141
&lt;/span&gt;&lt;span class="lnt"&gt;142
&lt;/span&gt;&lt;span class="lnt"&gt;143
&lt;/span&gt;&lt;span class="lnt"&gt;144
&lt;/span&gt;&lt;span class="lnt"&gt;145
&lt;/span&gt;&lt;span class="lnt"&gt;146
&lt;/span&gt;&lt;span class="lnt"&gt;147
&lt;/span&gt;&lt;span class="lnt"&gt;148
&lt;/span&gt;&lt;span class="lnt"&gt;149
&lt;/span&gt;&lt;span class="lnt"&gt;150
&lt;/span&gt;&lt;span class="lnt"&gt;151
&lt;/span&gt;&lt;span class="lnt"&gt;152
&lt;/span&gt;&lt;span class="lnt"&gt;153
&lt;/span&gt;&lt;span class="lnt"&gt;154
&lt;/span&gt;&lt;span class="lnt"&gt;155
&lt;/span&gt;&lt;span class="lnt"&gt;156
&lt;/span&gt;&lt;span class="lnt"&gt;157
&lt;/span&gt;&lt;span class="lnt"&gt;158
&lt;/span&gt;&lt;span class="lnt"&gt;159
&lt;/span&gt;&lt;span class="lnt"&gt;160
&lt;/span&gt;&lt;span class="lnt"&gt;161
&lt;/span&gt;&lt;span class="lnt"&gt;162
&lt;/span&gt;&lt;span class="lnt"&gt;163
&lt;/span&gt;&lt;span class="lnt"&gt;164
&lt;/span&gt;&lt;span class="lnt"&gt;165
&lt;/span&gt;&lt;span class="lnt"&gt;166
&lt;/span&gt;&lt;span class="lnt"&gt;167
&lt;/span&gt;&lt;span class="lnt"&gt;168
&lt;/span&gt;&lt;span class="lnt"&gt;169
&lt;/span&gt;&lt;span class="lnt"&gt;170
&lt;/span&gt;&lt;span class="lnt"&gt;171
&lt;/span&gt;&lt;span class="lnt"&gt;172
&lt;/span&gt;&lt;span class="lnt"&gt;173
&lt;/span&gt;&lt;span class="lnt"&gt;174
&lt;/span&gt;&lt;span class="lnt"&gt;175
&lt;/span&gt;&lt;span class="lnt"&gt;176
&lt;/span&gt;&lt;span class="lnt"&gt;177
&lt;/span&gt;&lt;span class="lnt"&gt;178
&lt;/span&gt;&lt;span class="lnt"&gt;179
&lt;/span&gt;&lt;span class="lnt"&gt;180
&lt;/span&gt;&lt;span class="lnt"&gt;181
&lt;/span&gt;&lt;span class="lnt"&gt;182
&lt;/span&gt;&lt;span class="lnt"&gt;183
&lt;/span&gt;&lt;span class="lnt"&gt;184
&lt;/span&gt;&lt;span class="lnt"&gt;185
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;aws-cdk-lib&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ec2&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;aws-cdk-lib/aws-ec2&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;ecs&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;aws-cdk-lib/aws-ecs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;efs&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;aws-cdk-lib/aws-efs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;iam&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;aws-cdk-lib/aws-iam&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;logs&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;aws-cdk-lib/aws-logs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;aws-cdk-lib/aws-s3&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;secretsmanager&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;aws-cdk-lib/aws-secretsmanager&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;constructs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;HermesAgentStack&lt;/span&gt; &lt;span class="kr"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;: &lt;span class="kt"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props?&lt;/span&gt;: &lt;span class="kt"&gt;cdk.StackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// --- Networking ---
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Use an existing VPC or create a new one.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// If creating new: this provisions public + private subnets with a NAT Gateway.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;vpc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Vpc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;HermesVpc&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;maxAzs&lt;/span&gt;: &lt;span class="kt"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;natGateways&lt;/span&gt;: &lt;span class="kt"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Security group: outbound HTTPS only (Slack, Bedrock, S3)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;taskSg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SecurityGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;TaskSg&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Hermes Agent - outbound HTTPS only&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;allowAllOutbound&lt;/span&gt;: &lt;span class="kt"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;taskSg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addEgressRule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Peer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;anyIpv4&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="nx"&gt;ec2&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Port&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tcp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;HTTPS outbound&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// --- Storage ---
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// EFS for persistent state (memories, skills, SQLite)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileSystem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;efs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FileSystem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;HermesState&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;encrypted&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;lifecyclePolicy&lt;/span&gt;: &lt;span class="kt"&gt;efs.LifecyclePolicy.AFTER_30_DAYS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;removalPolicy&lt;/span&gt;: &lt;span class="kt"&gt;cdk.RemovalPolicy.RETAIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;performanceMode&lt;/span&gt;: &lt;span class="kt"&gt;efs.PerformanceMode.GENERAL_PURPOSE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;accessPoint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fileSystem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addAccessPoint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;HermesAccess&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/hermes&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;createAcl&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ownerUid&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1000&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ownerGid&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1000&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;permissions&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;750&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;posixUser&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;uid&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1000&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;gid&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;1000&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Allow Fargate task to reach EFS (NFS port 2049)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;fileSystem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connections&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;allowDefaultPortFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskSg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// S3 bucket for knowledge base
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;knowledgeBucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;KnowledgeBucket&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;bucketName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`hermes-knowledge-&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;encryption&lt;/span&gt;: &lt;span class="kt"&gt;s3.BucketEncryption.S3_MANAGED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;removalPolicy&lt;/span&gt;: &lt;span class="kt"&gt;cdk.RemovalPolicy.RETAIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// --- Secrets ---
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slackBotToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;secretsmanager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;SlackBotToken&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;secretName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;hermes/slack-bot-token&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Slack Bot OAuth Token (xoxb-...)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;slackAppToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;secretsmanager&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;SlackAppToken&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;secretName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;hermes/slack-app-token&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;description&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Slack App-Level Token for Socket Mode (xapp-...)&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// --- ECS ---
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cluster&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ecs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Cluster&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Cluster&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;vpc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;clusterName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;hermes-agent&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;taskDef&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ecs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FargateTaskDefinition&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;TaskDef&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;cpu&lt;/span&gt;: &lt;span class="kt"&gt;512&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;memoryLimitMiB&lt;/span&gt;: &lt;span class="kt"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;runtimePlatform&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;cpuArchitecture&lt;/span&gt;: &lt;span class="kt"&gt;ecs.CpuArchitecture.X86_64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;operatingSystemFamily&lt;/span&gt;: &lt;span class="kt"&gt;ecs.OperatingSystemFamily.LINUX&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// --- IAM Permissions ---
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Bedrock: invoke Claude models (wildcard region for cross-region inference)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;taskDef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taskRole&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addToPrincipalPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PolicyStatement&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;bedrock:InvokeModel&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;bedrock:InvokeModelWithResponseStream&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;bedrock:ListFoundationModels&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;bedrock:ListInferenceProfiles&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;resources&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;arn:aws:bedrock:*::foundation-model/anthropic.*&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sb"&gt;`arn:aws:bedrock:*:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;:inference-profile/*`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sb"&gt;`arn:aws:bedrock:*:&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;account&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;:application-inference-profile/*`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// S3: read knowledge bucket
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;knowledgeBucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grantRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;taskDef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taskRole&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// EFS: mount and write
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;taskDef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;taskRole&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addToPrincipalPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PolicyStatement&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;elasticfilesystem:ClientMount&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;elasticfilesystem:ClientWrite&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;resources&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;fileSystem&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fileSystemArn&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// --- EFS Volume ---
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;taskDef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addVolume&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;hermes-state&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;efsVolumeConfiguration&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;fileSystemId&lt;/span&gt;: &lt;span class="kt"&gt;fileSystem.fileSystemId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;transitEncryption&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ENABLED&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;authorizationConfig&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;accessPointId&lt;/span&gt;: &lt;span class="kt"&gt;accessPoint.accessPointId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;iam&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ENABLED&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// --- Container ---
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;logGroup&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;logs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;LogGroup&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Logs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;logGroupName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/ecs/hermes-agent&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;retention&lt;/span&gt;: &lt;span class="kt"&gt;logs.RetentionDays.TWO_WEEKS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;removalPolicy&lt;/span&gt;: &lt;span class="kt"&gt;cdk.RemovalPolicy.DESTROY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;container&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;taskDef&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addContainer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hermes&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;image&lt;/span&gt;: &lt;span class="kt"&gt;ecs.ContainerImage.fromEcrRepository&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;aws_ecr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Repository&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fromRepositoryName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Repo&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;hermes-agent&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;HERMES_HOME&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/data/hermes&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;AWS_DEFAULT_REGION&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;us-east-1&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;KNOWLEDGE_BUCKET&lt;/span&gt;: &lt;span class="kt"&gt;knowledgeBucket.bucketName&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;secrets&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;SLACK_BOT_TOKEN&lt;/span&gt;: &lt;span class="kt"&gt;ecs.Secret.fromSecretsManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slackBotToken&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;SLACK_APP_TOKEN&lt;/span&gt;: &lt;span class="kt"&gt;ecs.Secret.fromSecretsManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;slackAppToken&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;logging&lt;/span&gt;: &lt;span class="kt"&gt;ecs.LogDrivers.awsLogs&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;streamPrefix&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;hermes&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;logGroup&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;healthCheck&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;command&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;CMD-SHELL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;true&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;interval&lt;/span&gt;: &lt;span class="kt"&gt;cdk.Duration.seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;timeout&lt;/span&gt;: &lt;span class="kt"&gt;cdk.Duration.seconds&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;retries&lt;/span&gt;: &lt;span class="kt"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addMountPoints&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;sourceVolume&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;hermes-state&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;containerPath&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;/data/hermes&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;readOnly&lt;/span&gt;: &lt;span class="kt"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// --- Fargate Service ---
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ecs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;FargateService&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;GatewayService&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;cluster&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;taskDefinition&lt;/span&gt;: &lt;span class="kt"&gt;taskDef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;desiredCount&lt;/span&gt;: &lt;span class="kt"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;securityGroups&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;taskSg&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;vpcSubnets&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;subnetType&lt;/span&gt;: &lt;span class="kt"&gt;ec2.SubnetType.PRIVATE_WITH_EGRESS&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;serviceName&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;hermes-gateway&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;enableExecuteCommand&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;circuitBreaker&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;enable&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rollback&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;minHealthyPercent&lt;/span&gt;: &lt;span class="kt"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;maxHealthyPercent&lt;/span&gt;: &lt;span class="kt"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// --- Outputs ---
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CfnOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;ClusterName&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;: &lt;span class="kt"&gt;cluster.clusterName&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CfnOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;BucketName&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;: &lt;span class="kt"&gt;knowledgeBucket.bucketName&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;cdk&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;CfnOutput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;EfsId&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;: &lt;span class="kt"&gt;fileSystem.fileSystemId&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="23-key-design-decisions-in-this-stack"&gt;
&lt;a href="#23-key-design-decisions-in-this-stack" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2.3 Key Design Decisions in This Stack
&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Choice&lt;/th&gt;
&lt;th&gt;Rationale&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;allowAllOutbound: false&lt;/code&gt; + explicit 443&lt;/td&gt;
&lt;td&gt;Least privilege. Only HTTPS traffic leaves the container.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;efs.LifecyclePolicy.AFTER_30_DAYS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Cold memories move to cheaper IA storage automatically.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;removalPolicy: RETAIN&lt;/code&gt; on EFS&lt;/td&gt;
&lt;td&gt;A &lt;code&gt;cdk destroy&lt;/code&gt; won&amp;rsquo;t wipe your agent&amp;rsquo;s memory.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;enableExecuteCommand: true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Allows &lt;code&gt;aws ecs execute-command&lt;/code&gt; to SSH into the container for debugging.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;minHealthyPercent: 0&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Allows full replacement of the single task during deploys (no spare capacity needed).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Health check &lt;code&gt;CMD-SHELL true&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No HTTP port exists (Socket Mode only). We just need liveness.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bedrock resource ARN with &lt;code&gt;*&lt;/code&gt; region&lt;/td&gt;
&lt;td&gt;Cross-region inference profiles can route to any region.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="24-deploy-infrastructure"&gt;
&lt;a href="#24-deploy-infrastructure" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2.4 Deploy Infrastructure
&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Create the ECR repository first (one-time)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws ecr create-repository --repository-name hermes-agent --region us-east-1
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Bootstrap CDK (first time only)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npx cdk bootstrap
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Deploy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npx cdk deploy
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="25-store-your-slack-tokens"&gt;
&lt;a href="#25-store-your-slack-tokens" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2.5 Store Your Slack Tokens
&lt;/h3&gt;
&lt;p&gt;After deployment creates the secrets, populate them:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws secretsmanager put-secret-value &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --secret-id hermes/slack-bot-token &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --secret-string &lt;span class="s2"&gt;&amp;#34;xoxb-your-bot-token-here&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws secretsmanager put-secret-value &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --secret-id hermes/slack-app-token &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --secret-string &lt;span class="s2"&gt;&amp;#34;xapp-your-app-token-here&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="step-3-docker-image"&gt;
&lt;a href="#step-3-docker-image" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 3: Docker Image
&lt;/h2&gt;
&lt;h3 id="31-dockerfile"&gt;
&lt;a href="#31-dockerfile" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
3.1 Dockerfile
&lt;/h3&gt;
&lt;p&gt;Create a &lt;code&gt;docker/&lt;/code&gt; directory with this &lt;code&gt;Dockerfile&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;span class="lnt"&gt;42
&lt;/span&gt;&lt;span class="lnt"&gt;43
&lt;/span&gt;&lt;span class="lnt"&gt;44
&lt;/span&gt;&lt;span class="lnt"&gt;45
&lt;/span&gt;&lt;span class="lnt"&gt;46
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;python:3.11-slim&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;AS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;base&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Optional: custom CA certificate injection (for corporate proxies)&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ARG&lt;/span&gt; CUSTOM_CA_CERT_BASE64&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -n &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$CUSTOM_CA_CERT_BASE64&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$CUSTOM_CA_CERT_BASE64&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; base64 -d &amp;gt; /usr/local/share/ca-certificates/custom.crt &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; update-ca-certificates&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# System dependencies&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; apt-get update &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get install -y --no-install-recommends &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; curl git ripgrep ca-certificates unzip &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; rm -rf /var/lib/apt/lists/*&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# AWS CLI v2 (for S3 sync in entrypoint)&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; curl -sS &lt;span class="s2"&gt;&amp;#34;https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip&amp;#34;&lt;/span&gt; -o &lt;span class="s2"&gt;&amp;#34;awscliv2.zip&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; unzip -q awscliv2.zip &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ./aws/install &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; rm -rf aws awscliv2.zip&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Node.js 20 (required for MCP servers which run via npx)&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; curl -fsSL https://deb.nodesource.com/setup_20.x &lt;span class="p"&gt;|&lt;/span&gt; bash - &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt-get install -y nodejs &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; rm -rf /var/lib/apt/lists/*&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Install uv (fast Python package manager) and Hermes Agent&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;SSL_CERT_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/ssl/certs/ca-certificates.crt&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;REQUESTS_CA_BUNDLE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/etc/ssl/certs/ca-certificates.crt&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; curl -LsSf https://astral.sh/uv/install.sh &lt;span class="p"&gt;|&lt;/span&gt; sh &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; /root/.local/bin/uv pip install --system &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;hermes-agent[all]&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; slack-bolt &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; slack-sdk &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;anthropic[bedrock]&amp;gt;=0.39.0&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;PATH&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/root/.local/bin:&lt;/span&gt;&lt;span class="nv"&gt;$PATH&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Non-root user (UID 1000 matches EFS access point)&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;RUN&lt;/span&gt; useradd -m -u &lt;span class="m"&gt;1000&lt;/span&gt; hermes&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;USER&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;hermes&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/home/hermes&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENV&lt;/span&gt; &lt;span class="nv"&gt;HERMES_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;/data/hermes&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Copy configuration and entrypoint&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; --chown&lt;span class="o"&gt;=&lt;/span&gt;hermes:hermes config.yaml /home/hermes/config.yaml&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; --chown&lt;span class="o"&gt;=&lt;/span&gt;hermes:hermes --chmod&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;755&lt;/span&gt; entrypoint.sh /home/hermes/entrypoint.sh&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/home/hermes/entrypoint.sh&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="32-why-each-layer"&gt;
&lt;a href="#32-why-each-layer" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
3.2 Why Each Layer
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;python:3.11-slim&lt;/strong&gt;: Small base, fewer CVEs than full image&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ripgrep&lt;/strong&gt;: Hermes uses it for fast text search in knowledge files&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS CLI v2&lt;/strong&gt;: The entrypoint syncs knowledge from S3 at runtime&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Node.js 20&lt;/strong&gt;: MCP servers (like Jira, GitHub integrations) run via &lt;code&gt;npx&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;uv&lt;/strong&gt;: 10–100x faster than pip for package installation&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;hermes-agent[all]&lt;/code&gt;&lt;/strong&gt;: Full agent framework with all extras (memory, MCP, vision, audio)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;anthropic[bedrock]&lt;/code&gt;&lt;/strong&gt;: Enables Bedrock as the inference provider (IAM auth, no API key needed)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;UID 1000&lt;/strong&gt;: Matches the EFS access point — files written by the container are owned correctly&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="33-build-and-push"&gt;
&lt;a href="#33-build-and-push" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
3.3 Build and Push
&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; docker/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Build (use --platform on Apple Silicon)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker build --platform linux/amd64 -t hermes-agent:latest .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Login to ECR&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws ecr get-login-password --region us-east-1 &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; docker login --username AWS --password-stdin &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;YOUR_ACCOUNT_ID&amp;gt;.dkr.ecr.us-east-1.amazonaws.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Tag and push&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker tag hermes-agent:latest &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;YOUR_ACCOUNT_ID&amp;gt;.dkr.ecr.us-east-1.amazonaws.com/hermes-agent:latest
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker push &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;lt;YOUR_ACCOUNT_ID&amp;gt;.dkr.ecr.us-east-1.amazonaws.com/hermes-agent:latest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Apple Silicon note&lt;/strong&gt;: The &lt;code&gt;--platform linux/amd64&lt;/code&gt; flag is required. Fargate only supports x86_64 images. Cross-compilation adds ~2 minutes to build time.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="step-4-configuration"&gt;
&lt;a href="#step-4-configuration" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 4: Configuration
&lt;/h2&gt;
&lt;p&gt;Create &lt;code&gt;docker/config.yaml&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;span class="lnt"&gt;42
&lt;/span&gt;&lt;span class="lnt"&gt;43
&lt;/span&gt;&lt;span class="lnt"&gt;44
&lt;/span&gt;&lt;span class="lnt"&gt;45
&lt;/span&gt;&lt;span class="lnt"&gt;46
&lt;/span&gt;&lt;span class="lnt"&gt;47
&lt;/span&gt;&lt;span class="lnt"&gt;48
&lt;/span&gt;&lt;span class="lnt"&gt;49
&lt;/span&gt;&lt;span class="lnt"&gt;50
&lt;/span&gt;&lt;span class="lnt"&gt;51
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;bedrock&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;us.anthropic.claude-sonnet-4-6 &lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c"&gt;# or claude-opus-4-7 for best quality&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;max_turns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;reasoning_effort&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;medium&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;system_prompt&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="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; You are a helpful AI assistant. You have access to files on disk
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; and can execute terminal commands to find information.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; When asked a question:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; 1. Check if relevant files exist in /data/hermes/knowledge/ or /data/hermes/projects/
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; 2. Read those files using terminal commands
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; 3. Synthesize your answer from what you find
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; 4. If you cannot find information, say so explicitly&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;memory_enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user_profile_enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;memory_char_limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2200&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;user_char_limit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1375&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;terminal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;backend&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;local&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;180&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;cwd: /data/hermes # CRITICAL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;must match your EFS mount path&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;compression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0.50&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;target_ratio&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0.20&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;display&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;streaming&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;show_cost&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;security&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;redact_secrets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;gateway&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;slack&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Optional: add MCP servers for external tool integrations&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# mcp_servers:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# github:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# command: npx&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# args: [&amp;#34;-y&amp;#34;, &amp;#34;@modelcontextprotocol/server-github&amp;#34;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# env:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# GITHUB_PERSONAL_ACCESS_TOKEN: &amp;#34;${GITHUB_TOKEN}&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="configuration-deep-dive"&gt;
&lt;a href="#configuration-deep-dive" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Configuration Deep Dive
&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;What It Does&lt;/th&gt;
&lt;th&gt;Why It Matters&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;model.provider: bedrock&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Use AWS Bedrock for inference&lt;/td&gt;
&lt;td&gt;IAM auth, no API key management&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;terminal.cwd: /data/hermes&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Working directory AND context discovery root&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;If wrong, the agent loses all project context&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;compression.enabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compresses long conversations&lt;/td&gt;
&lt;td&gt;Prevents context window overflow in extended sessions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gateway.slack.enabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Activates Slack Socket Mode listener&lt;/td&gt;
&lt;td&gt;The whole point of this deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;memory_enabled&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Persists learnings across conversations&lt;/td&gt;
&lt;td&gt;Agent improves over time, remembers preferences&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reasoning_effort: medium&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Balances quality and cost&lt;/td&gt;
&lt;td&gt;Use &lt;code&gt;high&lt;/code&gt; for complex multi-step tasks&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Critical&lt;/strong&gt;: &lt;code&gt;terminal.cwd&lt;/code&gt; serves a dual purpose. It sets the working directory for terminal commands AND is the starting point for discovering &lt;code&gt;.hermes.md&lt;/code&gt; (your project context file). If this doesn&amp;rsquo;t point to your EFS mount, the agent has no project context.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr&gt;
&lt;h2 id="step-5-the-entrypoint-script"&gt;
&lt;a href="#step-5-the-entrypoint-script" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 5: The Entrypoint Script
&lt;/h2&gt;
&lt;p&gt;Create &lt;code&gt;docker/entrypoint.sh&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;span class="lnt"&gt;42
&lt;/span&gt;&lt;span class="lnt"&gt;43
&lt;/span&gt;&lt;span class="lnt"&gt;44
&lt;/span&gt;&lt;span class="lnt"&gt;45
&lt;/span&gt;&lt;span class="lnt"&gt;46
&lt;/span&gt;&lt;span class="lnt"&gt;47
&lt;/span&gt;&lt;span class="lnt"&gt;48
&lt;/span&gt;&lt;span class="lnt"&gt;49
&lt;/span&gt;&lt;span class="lnt"&gt;50
&lt;/span&gt;&lt;span class="lnt"&gt;51
&lt;/span&gt;&lt;span class="lnt"&gt;52
&lt;/span&gt;&lt;span class="lnt"&gt;53
&lt;/span&gt;&lt;span class="lnt"&gt;54
&lt;/span&gt;&lt;span class="lnt"&gt;55
&lt;/span&gt;&lt;span class="lnt"&gt;56
&lt;/span&gt;&lt;span class="lnt"&gt;57
&lt;/span&gt;&lt;span class="lnt"&gt;58
&lt;/span&gt;&lt;span class="lnt"&gt;59
&lt;/span&gt;&lt;span class="lnt"&gt;60
&lt;/span&gt;&lt;span class="lnt"&gt;61
&lt;/span&gt;&lt;span class="lnt"&gt;62
&lt;/span&gt;&lt;span class="lnt"&gt;63
&lt;/span&gt;&lt;span class="lnt"&gt;64
&lt;/span&gt;&lt;span class="lnt"&gt;65
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -e
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;HERMES_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;HERMES_HOME&lt;/span&gt;&lt;span class="k"&gt;:-&lt;/span&gt;&lt;span class="p"&gt;/data/hermes&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Initialize HERMES_HOME (always update config from image)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir -p &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HERMES_HOME&lt;/span&gt;&lt;span class="s2"&gt;/memories&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HERMES_HOME&lt;/span&gt;&lt;span class="s2"&gt;/skills&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cp /home/hermes/config.yaml &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HERMES_HOME&lt;/span&gt;&lt;span class="s2"&gt;/config.yaml&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# S3 knowledge sync function&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sync_knowledge&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; -z &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$KNOWLEDGE_BUCKET&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;[&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;date -u +%H:%M:%S&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;] Syncing knowledge from s3://&lt;/span&gt;&lt;span class="nv"&gt;$KNOWLEDGE_BUCKET&lt;/span&gt;&lt;span class="s2"&gt;...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; mkdir -p &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HERMES_HOME&lt;/span&gt;&lt;span class="s2"&gt;/knowledge&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HERMES_HOME&lt;/span&gt;&lt;span class="s2"&gt;/projects&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Sync knowledge notes (--delete removes stale files)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; aws s3 sync &lt;span class="s2"&gt;&amp;#34;s3://&lt;/span&gt;&lt;span class="nv"&gt;$KNOWLEDGE_BUCKET&lt;/span&gt;&lt;span class="s2"&gt;/knowledge/&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HERMES_HOME&lt;/span&gt;&lt;span class="s2"&gt;/knowledge/&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --delete --quiet 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Sync project docs&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; aws s3 sync &lt;span class="s2"&gt;&amp;#34;s3://&lt;/span&gt;&lt;span class="nv"&gt;$KNOWLEDGE_BUCKET&lt;/span&gt;&lt;span class="s2"&gt;/projects/&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HERMES_HOME&lt;/span&gt;&lt;span class="s2"&gt;/projects/&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --quiet 2&amp;gt;/dev/null &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Compose SOUL.md (agent identity, loaded by gateway at startup)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; cat &amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HERMES_HOME&lt;/span&gt;&lt;span class="s2"&gt;/SOUL.md&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;&amp;lt; &amp;#39;IDENTITY&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;# Hermes — Your Knowledge Assistant
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;You are a knowledge assistant with access to project documentation and notes stored on disk.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;Your primary job is to answer questions by reading files — never from general knowledge alone.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;When asked about any topic:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;1. Search for relevant files using terminal commands (grep, cat, ls)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;2. Read and synthesize your answer from those files
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;3. If you cannot find the information, say so explicitly
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;Key paths:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;- `/data/hermes/knowledge/` — Topic-specific knowledge notes
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;- `/data/hermes/projects/` — Project documentation
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;IDENTITY&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;[&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;date -u +%H:%M:%S&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;] SOUL.md composed&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Initial sync at boot&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sync_knowledge
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Periodic re-sync every 15 minutes (background)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;while&lt;/span&gt; true&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sleep &lt;span class="m"&gt;900&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sync_knowledge
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Write .env for gateway credentials&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat &amp;gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$HERMES_HOME&lt;/span&gt;&lt;span class="s2"&gt;/.env&amp;#34;&lt;/span&gt; &lt;span class="s"&gt;&amp;lt;&amp;lt; EOF
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;SLACK_BOT_TOKEN=${SLACK_BOT_TOKEN}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;SLACK_APP_TOKEN=${SLACK_APP_TOKEN}
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;GATEWAY_ALLOW_ALL_USERS=true
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s"&gt;EOF&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Start gateway in foreground mode&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;exec&lt;/span&gt; hermes gateway run
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="boot-sequence-explained"&gt;
&lt;a href="#boot-sequence-explained" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Boot Sequence Explained
&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Container Start
│
├─ 1. Create directories: memories/, skills/
├─ 2. Copy config.yaml from image → EFS (always fresh)
├─ 3. S3 sync: download knowledge/, projects/
├─ 4. Compose SOUL.md (agent identity)
├─ 5. Start background sync loop (every 15 min)
├─ 6. Write .env (Slack tokens)
└─ 7. exec hermes gateway run
│
└─ Connects to Slack via Socket Mode
Listens for DMs and @mentions
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Why always overwrite config.yaml?&lt;/strong&gt; EFS is persistent. If you deployed a bad config once, it would stick forever. By always copying from the image, you guarantee that a fresh deploy always brings fresh configuration.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why &lt;code&gt;exec&lt;/code&gt;?&lt;/strong&gt; The &lt;code&gt;exec&lt;/code&gt; command replaces the shell process with the Hermes gateway. This means signals (SIGTERM from ECS during deployment) propagate directly to Hermes, enabling graceful shutdown.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why &lt;code&gt;|| true&lt;/code&gt; on S3 sync?&lt;/strong&gt; If the bucket is empty or the agent starts before knowledge is uploaded, we don&amp;rsquo;t want the container to crash. It will try again in 15 minutes.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="step-6-the-knowledge-system"&gt;
&lt;a href="#step-6-the-knowledge-system" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 6: The Knowledge System
&lt;/h2&gt;
&lt;p&gt;This is what makes the deployment truly useful. Hermes loads context from three independent slots:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Slot&lt;/th&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Char Limit&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Identity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;SOUL.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;20,000&lt;/td&gt;
&lt;td&gt;Who the agent is. Personality, key behaviors, available paths.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Project Context&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.hermes.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;20,000&lt;/td&gt;
&lt;td&gt;Domain knowledge. Loaded via &lt;code&gt;terminal.cwd&lt;/code&gt; discovery.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;System Prompt&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;config.yaml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Behavioral rules. Task-specific instructions.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="how-soulmd-works"&gt;
&lt;a href="#how-soulmd-works" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
How SOUL.md Works
&lt;/h3&gt;
&lt;p&gt;When the gateway starts, it looks for &lt;code&gt;SOUL.md&lt;/code&gt; in &lt;code&gt;$HERMES_HOME&lt;/code&gt;. This file defines the agent&amp;rsquo;s identity — think of it as the &amp;ldquo;who you are&amp;rdquo; prompt. It&amp;rsquo;s always loaded into every conversation, capped at 20,000 characters.&lt;/p&gt;
&lt;p&gt;Our entrypoint composes it at boot from a heredoc. You could also sync it from S3 for dynamic updates.&lt;/p&gt;
&lt;h3 id="how-hermesmd-works-optional"&gt;
&lt;a href="#how-hermesmd-works-optional" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
How .hermes.md Works (Optional)
&lt;/h3&gt;
&lt;p&gt;If you create a file called &lt;code&gt;.hermes.md&lt;/code&gt; at the &lt;code&gt;terminal.cwd&lt;/code&gt; path (&lt;code&gt;/data/hermes/.hermes.md&lt;/code&gt;), Hermes will automatically discover and load it as project context. This is useful for providing high-level documentation that should always be in context.&lt;/p&gt;
&lt;h3 id="knowledge-update-workflow-no-redeploy"&gt;
&lt;a href="#knowledge-update-workflow-no-redeploy" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Knowledge Update Workflow (No Redeploy)
&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;1. Upload files to S3:
aws s3 sync ./my-docs/ s3://hermes-knowledge-&amp;lt;ACCOUNT_ID&amp;gt;/knowledge/
2. Wait 15 minutes (background sync picks it up)
— OR —
Force immediate pickup:
aws ecs update-service --cluster hermes-agent \
--service hermes-gateway --force-new-deployment
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is the key operational advantage: your agent&amp;rsquo;s knowledge evolves independently of its container. Write a new doc, upload to S3, and the agent knows about it within 15 minutes.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="step-7-deploy-and-test"&gt;
&lt;a href="#step-7-deploy-and-test" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 7: Deploy and Test
&lt;/h2&gt;
&lt;h3 id="71-trigger-deployment"&gt;
&lt;a href="#71-trigger-deployment" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
7.1 Trigger Deployment
&lt;/h3&gt;
&lt;p&gt;If you already pushed the image and deployed infrastructure:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws ecs update-service &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --cluster hermes-agent &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --service hermes-gateway &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --force-new-deployment
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="72-watch-logs"&gt;
&lt;a href="#72-watch-logs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
7.2 Watch Logs
&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws logs tail /ecs/hermes-agent --since 5m --follow
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Success indicators:&lt;/strong&gt;&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;[14:30:01] Syncing knowledge from s3://hermes-knowledge-...
[14:30:05] SOUL.md composed
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If you see the gateway start without errors, the agent is live.&lt;/p&gt;
&lt;h3 id="73-talk-to-your-agent"&gt;
&lt;a href="#73-talk-to-your-agent" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
7.3 Talk to Your Agent
&lt;/h3&gt;
&lt;p&gt;Open Slack, find your bot in the Apps section (or DM it directly), and send:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Hello! What can you do?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You should get a response from Claude describing its capabilities.&lt;/p&gt;
&lt;h3 id="74-debug-with-ecs-exec"&gt;
&lt;a href="#74-debug-with-ecs-exec" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
7.4 Debug with ECS Exec
&lt;/h3&gt;
&lt;p&gt;If something goes wrong, shell into the running container:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Get the task ID&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;TASK_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;aws ecs list-tasks --cluster hermes-agent &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --service-name hermes-gateway --query &lt;span class="s1"&gt;&amp;#39;taskArns[0]&amp;#39;&lt;/span&gt; --output text &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; awk -F&lt;span class="s1"&gt;&amp;#39;/&amp;#39;&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;{print $NF}&amp;#39;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Shell in&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws ecs execute-command &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --cluster hermes-agent &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --task &lt;span class="nv"&gt;$TASK_ID&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --container hermes &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --interactive &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --command &lt;span class="s2"&gt;&amp;#34;/bin/bash&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Inside the container, check:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat /data/hermes/config.yaml &lt;span class="c1"&gt;# Config loaded correctly?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat /data/hermes/SOUL.md &lt;span class="c1"&gt;# Identity composed?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ls /data/hermes/knowledge/ &lt;span class="c1"&gt;# Knowledge synced?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat /data/hermes/.env &lt;span class="c1"&gt;# Tokens present?&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;hr&gt;
&lt;h2 id="troubleshooting"&gt;
&lt;a href="#troubleshooting" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Troubleshooting
&lt;/h2&gt;
&lt;p&gt;These are all real issues I hit during deployment. Each one cost me 30+ minutes the first time.&lt;/p&gt;
&lt;h3 id="1-ssltls-errors-during-docker-build"&gt;
&lt;a href="#1-ssltls-errors-during-docker-build" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1. SSL/TLS Errors During Docker Build
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; &lt;code&gt;pip install&lt;/code&gt; or &lt;code&gt;curl&lt;/code&gt; fails with &lt;code&gt;SSL certificate problem: unable to get local issuer certificate&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; Corporate VPN or proxy injecting a custom CA certificate that Docker&amp;rsquo;s build environment doesn&amp;rsquo;t trust.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Pass your custom CA cert as a base64 build arg:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker build &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --build-arg &lt;span class="nv"&gt;CUSTOM_CA_CERT_BASE64&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;base64 &amp;lt; /path/to/your-ca-cert.pem&lt;span class="k"&gt;)&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --platform linux/amd64 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -t hermes-agent:latest .
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Also: the &lt;code&gt;ENV SSL_CERT_FILE&lt;/code&gt; and &lt;code&gt;ENV REQUESTS_CA_BUNDLE&lt;/code&gt; in the Dockerfile handle Rust-based tools (like &lt;code&gt;uv&lt;/code&gt;) that maintain their own cert store.&lt;/p&gt;
&lt;h3 id="2-s3-sync-silently-fails"&gt;
&lt;a href="#2-s3-sync-silently-fails" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2. S3 Sync Silently Fails
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Knowledge files never appear on EFS, but no errors in logs (because of &lt;code&gt;|| true&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; IAM task role has &lt;code&gt;s3:GetObject&lt;/code&gt; but is missing &lt;code&gt;s3:ListBucket&lt;/code&gt;. The sync command needs to list before it can download.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Use CDK&amp;rsquo;s &lt;code&gt;bucket.grantRead()&lt;/code&gt; which grants both &lt;code&gt;s3:GetObject*&lt;/code&gt; and &lt;code&gt;s3:ListBucket&lt;/code&gt;. If you crafted IAM manually, add both.&lt;/p&gt;
&lt;h3 id="3-ecs-task-stuck-in-provisioning"&gt;
&lt;a href="#3-ecs-task-stuck-in-provisioning" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
3. ECS Task Stuck in PROVISIONING
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Task never reaches RUNNING status. Stays in PROVISIONING for minutes, then fails.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; Security group doesn&amp;rsquo;t allow NFS traffic (port 2049) between the Fargate task and the EFS file system.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; In CDK, use &lt;code&gt;fileSystem.connections.allowDefaultPortFrom(taskSg)&lt;/code&gt;. This automatically creates the correct security group rule. If you&amp;rsquo;re using CloudFormation directly, add an ingress rule on the EFS security group allowing port 2049 from the task security group.&lt;/p&gt;
&lt;h3 id="4-slack-missing_scope-errors"&gt;
&lt;a href="#4-slack-missing_scope-errors" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
4. Slack &amp;ldquo;missing_scope&amp;rdquo; Errors
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Bot connects but logs show &lt;code&gt;'error': 'missing_scope', 'needed': 'im:history'&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; Scopes were added AFTER the app was already installed to the workspace. Scope changes require re-installation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; After adding new scopes in the Slack app settings, go to &lt;strong&gt;OAuth &amp;amp; Permissions&lt;/strong&gt; and click &lt;strong&gt;&amp;ldquo;Reinstall to Workspace&amp;rdquo;&lt;/strong&gt;. This regenerates the bot token with the new scopes.&lt;/p&gt;
&lt;h3 id="5-agent-has-no-project-context-hermesmd-not-loading"&gt;
&lt;a href="#5-agent-has-no-project-context-hermesmd-not-loading" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
5. Agent Has No Project Context (.hermes.md Not Loading)
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Agent responds but seems to have no knowledge of your projects. SOUL.md loads fine, but .hermes.md is ignored.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; &lt;code&gt;terminal.cwd&lt;/code&gt; in config.yaml doesn&amp;rsquo;t point to where &lt;code&gt;.hermes.md&lt;/code&gt; lives. Hermes discovers &lt;code&gt;.hermes.md&lt;/code&gt; by walking up from &lt;code&gt;terminal.cwd&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Ensure &lt;code&gt;terminal.cwd: /data/hermes&lt;/code&gt; in your config.yaml AND that &lt;code&gt;.hermes.md&lt;/code&gt; exists at &lt;code&gt;/data/hermes/.hermes.md&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="6-mcp-server-fails-to-start"&gt;
&lt;a href="#6-mcp-server-fails-to-start" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
6. MCP Server Fails to Start
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; &lt;code&gt;&amp;quot;Failed to start MCP server&amp;quot;&lt;/code&gt; in logs. Agent works but external tools are unavailable.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; Node.js is not installed in the container. MCP servers run via &lt;code&gt;npx&lt;/code&gt; which requires Node.js.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Include the Node.js installation step in your Dockerfile. Also verify outbound HTTPS works (npx downloads packages from npm registry on first run).&lt;/p&gt;
&lt;h3 id="7-bedrock-accessdeniedexception"&gt;
&lt;a href="#7-bedrock-accessdeniedexception" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
7. Bedrock AccessDeniedException
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; Agent connects to Slack but every response is an error. Logs show &lt;code&gt;AccessDeniedException&lt;/code&gt; from Bedrock.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; IAM policy restricts Bedrock access to a single region, but cross-region inference profiles (the &lt;code&gt;us.&lt;/code&gt; prefix models) route to any region.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Use wildcard &lt;code&gt;*&lt;/code&gt; for the region portion of the Bedrock resource ARN:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;arn:aws:bedrock:*::foundation-model/anthropic.*
arn:aws:bedrock:*:&amp;lt;ACCOUNT&amp;gt;:inference-profile/*
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="8-container-keeps-restarting-health-check-failure"&gt;
&lt;a href="#8-container-keeps-restarting-health-check-failure" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
8. Container Keeps Restarting (Health Check Failure)
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; ECS repeatedly stops and restarts the task. Logs show &amp;ldquo;container marked unhealthy.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; Default health check expects an HTTP response, but Hermes has no HTTP port — it only uses outbound WebSocket (Socket Mode).&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Use &lt;code&gt;CMD-SHELL true&lt;/code&gt; as the health check command. This always passes. You rely on the circuit breaker for actual failure detection (crash loop protection).&lt;/p&gt;
&lt;h3 id="9-config-changes-not-taking-effect"&gt;
&lt;a href="#9-config-changes-not-taking-effect" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
9. Config Changes Not Taking Effect
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Symptom:&lt;/strong&gt; You updated &lt;code&gt;config.yaml&lt;/code&gt;, rebuilt the image, pushed, and force-deployed. But the agent still behaves with old config.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Root Cause:&lt;/strong&gt; The Hermes gateway caches the &lt;code&gt;AIAgent&lt;/code&gt; object at startup. Even though the entrypoint overwrites &lt;code&gt;config.yaml&lt;/code&gt; on EFS, the gateway only reads it once.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Solution:&lt;/strong&gt; Force a new deployment: &lt;code&gt;aws ecs update-service --force-new-deployment&lt;/code&gt;. This kills the old task and starts fresh. Knowledge changes (S3 files read at query time) don&amp;rsquo;t need restarts — only config changes do.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="cost-breakdown"&gt;
&lt;a href="#cost-breakdown" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Cost Breakdown
&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Resource&lt;/th&gt;
&lt;th&gt;Monthly Cost&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ECS Fargate (512 CPU, 1024 MiB, 24/7)&lt;/td&gt;
&lt;td&gt;~$15&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NAT Gateway (fixed hourly + data transfer)&lt;/td&gt;
&lt;td&gt;~$35–45&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EFS (1 GB, mostly Infrequent Access)&lt;/td&gt;
&lt;td&gt;~$0.50&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;S3 (10 MB knowledge files)&lt;/td&gt;
&lt;td&gt;~$0.01&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Secrets Manager (2 secrets)&lt;/td&gt;
&lt;td&gt;~$0.80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CloudWatch Logs (2 weeks retention)&lt;/td&gt;
&lt;td&gt;~$1–3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Subtotal (infrastructure)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~$55–65&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bedrock inference (varies by usage)&lt;/td&gt;
&lt;td&gt;$5–100+&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 id="cost-optimization-tips"&gt;
&lt;a href="#cost-optimization-tips" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Cost Optimization Tips
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;NAT Gateway is the biggest fixed cost.&lt;/strong&gt; For personal projects, consider:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A &lt;strong&gt;NAT Instance&lt;/strong&gt; on &lt;code&gt;t4g.nano&lt;/code&gt; (~$4/month) instead of a managed NAT Gateway&lt;/li&gt;
&lt;li&gt;Or deploy in a &lt;strong&gt;public subnet&lt;/strong&gt; with a public IP (simpler but less secure)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Use Sonnet instead of Opus&lt;/strong&gt; for routine queries. Opus costs 5–10x more per token. Save Opus for complex reasoning tasks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;EFS Infrequent Access&lt;/strong&gt; automatically moves files untouched for 30 days to cheaper storage (from $0.30/GB to $0.025/GB).&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="next-steps"&gt;
&lt;a href="#next-steps" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Next Steps
&lt;/h2&gt;
&lt;p&gt;Once your agent is running, here&amp;rsquo;s what to explore:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Add MCP Servers&lt;/strong&gt; — Give your agent tools. Jira, GitHub, Linear, databases — anything with an MCP-compatible server. Add them to the &lt;code&gt;mcp_servers:&lt;/code&gt; section of config.yaml.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Automate Knowledge Updates&lt;/strong&gt; — Set up a CI/CD pipeline that syncs documentation to S3 on every merge. Your agent&amp;rsquo;s knowledge stays current without manual intervention.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Multi-Agent Architecture&lt;/strong&gt; — Run a second Hermes instance with a different &lt;code&gt;SOUL.md&lt;/code&gt; and config. One agent for engineering questions, another for project management, another for code review.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Scheduled Tasks&lt;/strong&gt; — Use EventBridge to periodically invoke prompts. Daily standup summaries, weekly reports, automated checks.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Access Control&lt;/strong&gt; — Set &lt;code&gt;GATEWAY_ALLOW_ALL_USERS=false&lt;/code&gt; and configure an allowlist. Restrict which Slack users can interact with the agent.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="wrapping-up"&gt;
&lt;a href="#wrapping-up" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Wrapping Up
&lt;/h2&gt;
&lt;p&gt;What we&amp;rsquo;ve built is a production-grade AI assistant deployment: always-on, team-accessible, with persistent memory and a self-updating knowledge base. The Socket Mode architecture eliminates the typical networking headaches of Slack bots, and the S3 knowledge sync means your agent gets smarter without redeployments.&lt;/p&gt;
&lt;p&gt;The entire infrastructure is defined as code (CDK), the container is reproducible (Docker), and the knowledge system is just files in a bucket. No vendor lock-in beyond AWS Bedrock for inference — and even that could be swapped by changing &lt;code&gt;model.provider&lt;/code&gt; in config.yaml.&lt;/p&gt;
&lt;p&gt;If you want to see the Hermes Agent project: &lt;a href="https://github.com/NousResearch/hermes-agent"&gt;github.com/NousResearch/hermes-agent&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Happy deploying.&lt;/p&gt;</description></item><item><title>Personal AWS Cost Guardrails: Budget Alerts, Auto-Stop, and Anomaly Detection on a Hobby Account</title><link>https://www.yopa.page/blog/2026-03-30-personal-aws-cost-guardrails.html</link><pubDate>Mon, 30 Mar 2026 12:35:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-03-30-personal-aws-cost-guardrails.html</guid><description>
&lt;p&gt;There&amp;rsquo;s a recurring genre of AWS war story — &amp;ldquo;I left a script running, woke up to a $4,000 bill, AWS support waived it.&amp;rdquo; It&amp;rsquo;s a real risk on a hobby account: you don&amp;rsquo;t have an org-level SCP, you don&amp;rsquo;t have a FinOps team, and one misconfigured loop on Bedrock or a forgotten EC2 instance can quietly run for a week.&lt;/p&gt;
&lt;p&gt;So I sat down and built the smallest set of guardrails that would actually stop me, not just notify me. Three layers. All deployable from one CloudFormation stack except the last one, which has a constraint AWS doesn&amp;rsquo;t document well.&lt;/p&gt;
&lt;h2 id="what-im-protecting-against"&gt;
&lt;a href="#what-im-protecting-against" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What I&amp;rsquo;m protecting against
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Steady drift&lt;/strong&gt; — I forget the EC2 instance is up, it racks up $30 over a month. Annoying, not catastrophic.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Surprise spike&lt;/strong&gt; — I push a buggy script that hammers Bedrock or starts a too-big instance. Could be hundreds of dollars in hours.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Slow leak&lt;/strong&gt; — Some service I provisioned a year ago is still billing me $3/day. Death by a thousand papercuts.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Different time horizons need different tools.&lt;/p&gt;
&lt;h2 id="layer-1--budget-alerts-the-slow-drift"&gt;
&lt;a href="#layer-1--budget-alerts-the-slow-drift" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Layer 1 — Budget alerts (the slow drift)
&lt;/h2&gt;
&lt;p&gt;AWS Budgets fires email alerts at thresholds against a monthly cost target. Not real-time — Budgets evaluates roughly every 8–12 hours — but cheap and zero-maintenance.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;MonthlyBudget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;AWS::Budgets::Budget&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Budget&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;BudgetName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;monthly-cost-budget&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;BudgetType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;COST&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;TimeUnit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;MONTHLY&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;BudgetLimit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Amount&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;40&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Unit&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;USD&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;NotificationsWithSubscribers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;Notification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;NotificationType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ACTUAL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ComparisonOperator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;GREATER_THAN&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;50&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ThresholdType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;PERCENTAGE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Subscribers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;SubscriptionType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;SNS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="l"&gt;Ref CostAlertTopic&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;Notification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;NotificationType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ACTUAL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ComparisonOperator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;GREATER_THAN&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ThresholdType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;PERCENTAGE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Subscribers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;SubscriptionType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;SNS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="l"&gt;Ref CostAlertTopic&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;Notification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;NotificationType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ACTUAL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ComparisonOperator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;GREATER_THAN&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ThresholdType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;PERCENTAGE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Subscribers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="nt"&gt;SubscriptionType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;SNS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="l"&gt;Ref CostAlertTopic&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;50% / 80% / 100% is the common shape. The 50% notification is the one that&amp;rsquo;s actually useful — if I&amp;rsquo;m halfway through the month and at 50%, I&amp;rsquo;m tracking. If I&amp;rsquo;m one week in and at 50%, something is wrong.&lt;/p&gt;
&lt;h2 id="layer-2--auto-stop-lambda-the-spike-safety-net"&gt;
&lt;a href="#layer-2--auto-stop-lambda-the-spike-safety-net" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Layer 2 — Auto-stop Lambda (the spike safety net)
&lt;/h2&gt;
&lt;p&gt;Email alerts at 100% are nice but I&amp;rsquo;m not going to read email at 2am. So the 100% threshold &lt;em&gt;also&lt;/em&gt; fires an SNS message that triggers a Lambda, which stops every running EC2 instance tagged with &lt;code&gt;AutoStop=true&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;AutoStopFunction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;AWS::Lambda::Function&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;FunctionName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;cost-monitor-auto-stop&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;python3.13&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;index.handler&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="l"&gt;GetAtt AutoStopRole.Arn&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;60&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Code&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;ZipFile&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="sd"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; import boto3
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; ec2 = boto3.client(&amp;#39;ec2&amp;#39;)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; def handler(event, context):
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; r = ec2.describe_instances(Filters=[
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; {&amp;#39;Name&amp;#39;: &amp;#39;tag:AutoStop&amp;#39;, &amp;#39;Values&amp;#39;: [&amp;#39;true&amp;#39;]},
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; {&amp;#39;Name&amp;#39;: &amp;#39;instance-state-name&amp;#39;, &amp;#39;Values&amp;#39;: [&amp;#39;running&amp;#39;]},
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; ])
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; ids = [i[&amp;#39;InstanceId&amp;#39;] for res in r[&amp;#39;Reservations&amp;#39;] for i in res[&amp;#39;Instances&amp;#39;]]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; if ids:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; ec2.stop_instances(InstanceIds=ids)
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="sd"&gt; return {&amp;#39;stopped&amp;#39;: ids}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;AutoStopSubscription&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;AWS::SNS::Subscription&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;TopicArn&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="l"&gt;Ref CostAlertTopic&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;lambda&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;!&lt;span class="l"&gt;GetAtt AutoStopFunction.Arn&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This is &lt;strong&gt;not&lt;/strong&gt; a real-time circuit breaker. Budgets evaluates every 8–12 hours, so the auto-stop only kicks in within that window. If you launch a &lt;code&gt;p4d.24xlarge&lt;/code&gt; and it costs $40/hour, you can blow past your $40 monthly budget in one hour and Budgets won&amp;rsquo;t notice until the next evaluation cycle.&lt;/p&gt;
&lt;p&gt;The auto-stop is for the case where you&amp;rsquo;ve been &lt;em&gt;gradually&lt;/em&gt; burning toward your monthly limit and want a &amp;ldquo;no, really, stop spending&amp;rdquo; backstop. For genuine spike protection, you need Layer 3.&lt;/p&gt;
&lt;p&gt;The IAM role is small — just &lt;code&gt;ec2:DescribeInstances&lt;/code&gt; and &lt;code&gt;ec2:StopInstances&lt;/code&gt;, scoped to the account. No reason to grant &lt;code&gt;Start&lt;/code&gt; on the auto-stop role; restarting is a manual decision.&lt;/p&gt;
&lt;h2 id="layer-3--cost-anomaly-detection-the-spike-detector"&gt;
&lt;a href="#layer-3--cost-anomaly-detection-the-spike-detector" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Layer 3 — Cost Anomaly Detection (the spike detector)
&lt;/h2&gt;
&lt;p&gt;This is the one that catches &amp;ldquo;you launched something weird two hours ago.&amp;rdquo; Cost Anomaly Detection (CAD) evaluates roughly daily on actual usage, looks for statistical anomalies vs. your historical baseline per service, and emails you when one service&amp;rsquo;s daily spend deviates significantly.&lt;/p&gt;
&lt;p&gt;The trick: you can&amp;rsquo;t fully manage CAD from CloudFormation if you already have a manually-created monitor. AWS allows exactly &lt;strong&gt;one DIMENSIONAL/SERVICE anomaly monitor per account&lt;/strong&gt;. If you click around in the console and create one, then later try to create one in CloudFormation:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;AlreadyExistsException: AnomalyMonitor already exists.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The fix is one of:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Delete the manual one, then create via CloudFormation.&lt;/li&gt;
&lt;li&gt;Skip CloudFormation, manage CAD by hand via the console.&lt;/li&gt;
&lt;li&gt;Import the existing resource into your stack (&lt;code&gt;aws cloudformation create-change-set --change-set-type IMPORT ...&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;I went with option 2 because CAD is set-and-forget. The monitor lives outside the stack; the stack manages the budget + auto-stop + SNS topic.&lt;/p&gt;
&lt;p&gt;If you do go via CloudFormation, two more gotchas:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;ThresholdExpression&lt;/code&gt; is a &lt;strong&gt;JSON string&lt;/strong&gt;, not a YAML object. CFN&amp;rsquo;s parser will accept the YAML and then fail at deploy:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# correct&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;ThresholdExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;{&amp;#34;Dimensions&amp;#34;:{&amp;#34;Key&amp;#34;:&amp;#34;ANOMALY_TOTAL_IMPACT_ABSOLUTE&amp;#34;,&amp;#34;MatchOptions&amp;#34;:[&amp;#34;GREATER_THAN_OR_EQUAL&amp;#34;],&amp;#34;Values&amp;#34;:[&amp;#34;5&amp;#34;]}}&amp;#39;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# wrong — fails EarlyValidation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;ThresholdExpression&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Dimensions&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ANOMALY_TOTAL_IMPACT_ABSOLUTE&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;Values&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="s1"&gt;&amp;#39;5&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;MatchOptions&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="s1"&gt;&amp;#39;GREATER_THAN_OR_EQUAL&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Frequency: IMMEDIATE&lt;/code&gt; only works with SNS subscribers. If you want email straight to your inbox, the supported frequencies are &lt;code&gt;DAILY&lt;/code&gt; and &lt;code&gt;WEEKLY&lt;/code&gt;. I went with &lt;code&gt;DAILY&lt;/code&gt; and a $5 absolute-impact threshold — anything that bumps a single service by $5 in a day is something I want to know about within 24 hours.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-this-catches-and-what-it-doesnt"&gt;
&lt;a href="#what-this-catches-and-what-it-doesnt" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What this catches and what it doesn&amp;rsquo;t
&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Scenario&lt;/th&gt;
&lt;th&gt;Caught by&lt;/th&gt;
&lt;th&gt;Latency&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Forgotten EC2 instance, ~$30 over a month&lt;/td&gt;
&lt;td&gt;Budget at 50% / 80%&lt;/td&gt;
&lt;td&gt;~12 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bedrock loop bug, $200 in one afternoon&lt;/td&gt;
&lt;td&gt;Anomaly Detection&lt;/td&gt;
&lt;td&gt;~24 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;p4d&lt;/code&gt; launched accidentally&lt;/td&gt;
&lt;td&gt;None of the above, fast enough&lt;/td&gt;
&lt;td&gt;Hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slow leak: $3/day for a year&lt;/td&gt;
&lt;td&gt;Anomaly Detection threshold (if $5+)&lt;/td&gt;
&lt;td&gt;DAILY email&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Honest assessment: a determined accident in a single hour can still hurt. AWS doesn&amp;rsquo;t really offer real-time auto-stop at any reasonable price point — Budgets is the closest thing and it&amp;rsquo;s not real-time. If you genuinely need spike protection on the order of minutes, your options are (a) provision through Service Catalog with hard-coded instance type allowlists, (b) put an SCP on a proper org account, or (c) keep most of your spend on a fixed-cost subscription (Reserved/Savings Plans) so the variable portion can&amp;rsquo;t run away.&lt;/p&gt;
&lt;p&gt;For a hobby account: this three-layer setup is what I&amp;rsquo;d call &amp;ldquo;good enough that I don&amp;rsquo;t lose sleep, cheap enough that it costs me nothing on top of what I&amp;rsquo;m already spending.&amp;rdquo;&lt;/p&gt;
&lt;h2 id="pitfalls-i-actually-hit"&gt;
&lt;a href="#pitfalls-i-actually-hit" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pitfalls I actually hit
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;AlreadyExists&lt;/code&gt; on the anomaly monitor.&lt;/strong&gt; If you&amp;rsquo;ve ever clicked &amp;ldquo;set up anomaly detection&amp;rdquo; in the console, you have a DIMENSIONAL/SERVICE monitor and CFN can&amp;rsquo;t make a second one. Either import it or skip CFN for CAD.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;ThresholdExpression&lt;/code&gt; shape.&lt;/strong&gt; Must be a JSON-encoded string. CFN templates accept YAML for most properties so this looks like a typo, not a constraint.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;Frequency: IMMEDIATE&lt;/code&gt; with email.&lt;/strong&gt; Doesn&amp;rsquo;t work. Use SNS-then-email if you want IMMEDIATE; use DAILY/WEEKLY for direct email.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Auto-stop ≠ circuit breaker.&lt;/strong&gt; Budgets is not real-time. If you write a tight Bedrock loop, the auto-stop fires hours after you&amp;rsquo;ve already spent the money.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Tag your stoppable instances.&lt;/strong&gt; The Lambda filters on &lt;code&gt;AutoStop=true&lt;/code&gt;. If you forget the tag, the Lambda runs successfully and stops nothing. Tag every personal EC2 instance with &lt;code&gt;AutoStop=true&lt;/code&gt; by default; remove the tag explicitly when you want something to keep running.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Restart is manual on purpose.&lt;/strong&gt; The Lambda has no &lt;code&gt;ec2:StartInstances&lt;/code&gt;. Restarting an instance after auto-stop should be a conscious &amp;ldquo;okay, I know what&amp;rsquo;s running and why&amp;rdquo; decision, not an automatic recovery.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="what-to-do"&gt;
&lt;a href="#what-to-do" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What to do
&lt;/h2&gt;
&lt;p&gt;If you have a personal AWS account and you don&amp;rsquo;t have at least Layer 1 (a budget with email alerts), set that up today — it&amp;rsquo;s ten minutes in the console, free, and prevents the steady-drift class of problem.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re running anything that bills by the hour (EC2, RDS, OpenSearch, GPU anything), add Layer 2. The Lambda is twenty lines and a tag.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re using usage-billed services where one bug can spike fast (Bedrock, Lambda invocation count, S3 request count), add Layer 3 — DAILY anomaly emails at a $5 absolute threshold catch the &amp;ldquo;what is this charge&amp;rdquo; stuff before it becomes a &amp;ldquo;what &lt;em&gt;was&lt;/em&gt; this charge&amp;rdquo; conversation.&lt;/p&gt;
&lt;p&gt;Three layers, ~20 minutes total, zero ongoing cost. Worth it.&lt;/p&gt;</description></item><item><title>AWS Strands vs LangGraph on Bedrock AgentCore: Lessons from Building an Agentic Platform</title><link>https://www.yopa.page/blog/2026-02-28-strands-vs-langgraph.html</link><pubDate>Sat, 28 Feb 2026 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-02-28-strands-vs-langgraph.html</guid><description>
&lt;blockquote&gt;
&lt;p&gt;Comparing AWS Strands and LangGraph through hands-on PoC experience on Amazon Bedrock AgentCore — real cold start numbers, a three-tier tool architecture pattern, and the async tool gap that caught us off guard.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://strandsagents.com/docs/user-guide/quickstart/python/"&gt;AWS Strands Agents SDK&lt;/a&gt; |
&lt;a href="https://docs.langchain.com/oss/python/langgraph/overview"&gt;LangGraph Documentation&lt;/a&gt; |
&lt;a href="https://aws.amazon.com/bedrock/agentcore/"&gt;Amazon Bedrock AgentCore&lt;/a&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Review note (2026-08-28):&lt;/strong&gt; The measurements and deployment observations below are a February 2026 PoC snapshot. Strands, LangGraph, and AgentCore release independently; re-check the linked documentation and package versions before copying the code into a new deployment.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Most framework comparisons stop at &amp;ldquo;hello world.&amp;rdquo; You read them, nod along, then hit a wall that the blog never mentioned. This post is the opposite of that. I want to share what actually happened when we ran both AWS Strands and LangGraph through PoCs on a financial SaaS platform, picked one, and started building a real multi-tool agent with it. Spoiler: we found a gap that made us rethink our confidence level.&lt;/p&gt;
&lt;h2 id="the-setup-why-we-needed-an-agent-framework"&gt;
&lt;a href="#the-setup-why-we-needed-an-agent-framework" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Setup: Why We Needed an Agent Framework
&lt;/h2&gt;
&lt;p&gt;Our platform is 100% AWS-native. The primary execution environment is &lt;strong&gt;Amazon Bedrock AgentCore&lt;/strong&gt; — think of it as a managed runtime for AI agents with built-in memory, tool routing, and IAM-based auth. We needed to build agentic workflows that could handle 51 backend actions (knowledge base search, document extraction, custom AI actions, etc.) for financial domain customers.&lt;/p&gt;
&lt;p&gt;Two candidates made the shortlist:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;LangGraph&lt;/strong&gt; (LangChain ecosystem) — explicit state machines, large community, mature docs&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS Strands&lt;/strong&gt; (AWS-native) — autonomous agent loop, &lt;code&gt;@tool&lt;/code&gt; decorators, minimal boilerplate&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We PoC&amp;rsquo;d both, picked Strands, and started building a real agent with 25+ tools on it. The story isn&amp;rsquo;t as simple as &amp;ldquo;X is better.&amp;rdquo; It&amp;rsquo;s more like &amp;ldquo;X works great until you hit &lt;em&gt;this&lt;/em&gt; wall.&amp;rdquo;&lt;/p&gt;
&lt;h2 id="what-killed-langgraph-in-our-poc"&gt;
&lt;a href="#what-killed-langgraph-in-our-poc" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What Killed LangGraph in Our PoC
&lt;/h2&gt;
&lt;p&gt;I want to be clear: LangGraph is a solid framework. The state machine model gives you explicit control over every step — when the LLM gets called, when tools execute, how results route. For complex multi-step workflows, that&amp;rsquo;s powerful. But in our environment, it never got the chance to show that.&lt;/p&gt;
&lt;h3 id="the-30-second-observation-historical"&gt;
&lt;a href="#the-30-second-observation-historical" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The 30-Second Observation (Historical)
&lt;/h3&gt;
&lt;p&gt;During this PoC we observed a &lt;strong&gt;30-second initialization problem&lt;/strong&gt; when native dependencies were installed during startup. That observation should not be copied as a universal AgentCore contract: current AgentCore documentation exposes configurable runtime lifecycle settings and does not document a blanket 30-second initialization limit. LangGraph still relies on packages such as &lt;code&gt;numpy&lt;/code&gt; and &lt;code&gt;pydantic-core&lt;/code&gt;, so build and measure the image for your selected architecture rather than installing native wheels during a cold start.&lt;/p&gt;
&lt;p&gt;The workaround exists: use Docker-based Lambda build containers (&lt;code&gt;public.ecr.aws/sam/build-python3.11&lt;/code&gt;), pre-compile ARM64 wheels, bundle them into your CDK code assets before deployment. It works. But it also means every code change goes through a Docker build step that kills your iteration speed. For a PoC where you&amp;rsquo;re experimenting rapidly, that friction adds up fast. Trust me, we tried to make it work for a while before moving on.&lt;/p&gt;
&lt;p&gt;We confirmed this wasn&amp;rsquo;t a one-time issue in our PoC — native dependency installation remained the fundamental constraint for that build. &lt;strong&gt;LangGraph remained blocked for our use case at that snapshot&lt;/strong&gt;, unless we moved compilation into the image build or changed the dependency footprint. Re-test this conclusion against the current runtime and package versions.&lt;/p&gt;
&lt;h3 id="the-ecosystem-advantage-is-real-though"&gt;
&lt;a href="#the-ecosystem-advantage-is-real-though" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Ecosystem Advantage is Real Though
&lt;/h3&gt;
&lt;p&gt;I want to acknowledge something: LangGraph has significantly better documentation and community support. It launched earlier, so there are more reference implementations, more Stack Overflow answers, more patterns to copy. When we hit issues with Strands, the answer was often &amp;ldquo;read the Bedrock docs and figure it out.&amp;rdquo; With LangGraph, someone had usually already solved it. That gap matters during the learning curve. It just didn&amp;rsquo;t outweigh the deployment blocker for us.&lt;/p&gt;
&lt;h2 id="strands-in-practice-what-we-actually-built"&gt;
&lt;a href="#strands-in-practice-what-we-actually-built" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Strands in Practice: What We Actually Built
&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s our agent entry point. The whole thing:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# agent.py — 26 lines total&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;strands.models&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BedrockModel&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BedrockModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;model_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;global.anthropic.claude-sonnet-4-6&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;streaming&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system_prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_input&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Autonomous loop handles everything&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;That&amp;rsquo;s it. No state machine definition, no node compilation, no conditional edge routing. The agent loop decides when to call tools, when to respond, when to ask for clarification. You hand it tools and a prompt, it figures out the rest.&lt;/p&gt;
&lt;p&gt;For comparison, the LangGraph equivalent would need roughly 80-100 lines: state class definition, chatbot node, tools node, conditional edge routing function, graph compilation, and invocation boilerplate.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Cold starts in this PoC?&lt;/strong&gt; Under 500ms. We used UV bytecode compilation (&lt;code&gt;UV_COMPILE_BYTECODE=1&lt;/code&gt; in our Dockerfile) and measured a prebuilt image with no native dependency compilation. Treat that number as a dated workload measurement, not an AgentCore guarantee.&lt;/p&gt;
&lt;h3 id="the-three-tier-tool-architecture"&gt;
&lt;a href="#the-three-tier-tool-architecture" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Three-Tier Tool Architecture
&lt;/h3&gt;
&lt;p&gt;This is the pattern we landed on through our PoC iterations. It wasn&amp;rsquo;t planned upfront — it emerged from hitting real constraints around latency, security, and multi-tenant isolation:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;┌─────────────────────────────────────────────┐
│ Strands Agent (Claude Sonnet 4) │
└──────────────────┬──────────────────────────┘
│
┌─────────────┼─────────────────┐
▼ ▼ ▼
┌─────────┐ ┌──────────┐ ┌──────────────┐
│ Direct │ │ Gateway │ │ Tool │
│ Tools │ │ Tools │ │ Wrappers │
│ (15×) │ │ (10×) │ │ │
└─────────┘ └──────────┘ └──────────────┘
│ @tool │ MCP proto │ Auto-inject
│ decorator │ Lambda │ tenant_id
│ In-process │ backends │ Hidden from
│ execution │ IAM auth │ LLM context
└────────────┴────────────┴───────────────
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;Why three tiers?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Direct Tools&lt;/strong&gt; run inside the agent container. They&amp;rsquo;re simple functions decorated with &lt;code&gt;@tool&lt;/code&gt; — Strands auto-extracts the JSON schema from the function signature and docstring. No manual schema definition needed. We have 15 of these for low-latency, same-tenant operations. Average: ~24 lines per tool including docstring and serialization.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what a direct tool looks like (simplified from our actual implementation):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;search_knowledge_base&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_results&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;Search the knowledge base for relevant documents.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bedrock-agent-runtime&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;retrieve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;knowledgeBaseId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;KB_ID&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;retrievalQuery&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;retrievalConfiguration&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;vectorSearchConfiguration&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;numberOfResults&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;max_results&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;content&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;score&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;score&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;retrievalResults&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;results&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;count&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;~16 lines. In LangGraph, the same tool needs a state TypedDict, a node function, graph wiring, and compilation — roughly 40-50 lines. Multiply that by 15 tools and the difference is significant.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Gateway Tools&lt;/strong&gt; go through MCP (Model Context Protocol) to Lambda backends with SigV4 authentication. These handle cross-tenant operations or operations that need their own execution context. We define them in CDK and the MCP client discovers them at runtime.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tool Wrappers&lt;/strong&gt; solve a subtle but important problem: tenant isolation. The LLM should never see or manipulate &lt;code&gt;tenant_id&lt;/code&gt; directly — that&amp;rsquo;s a security boundary. Wrappers auto-inject the tenant ID into tool calls before they hit the backend, keeping it invisible to the model:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Simplified wrapper pattern&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrap_tool_with_tenant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nd"&gt;@wraps&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tool_fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrapped&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;tenant_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tenant_id&lt;/span&gt; &lt;span class="c1"&gt;# Injected, never from LLM&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;tool_fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;wrapped&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This pattern was born from a real security review, not from upfront design. The LLM was occasionally hallucinating tenant IDs in early testing — yeah, it just made up tenant IDs that looked plausible. In a financial platform, that&amp;rsquo;s a nightmare. Wrappers killed that problem entirely.&lt;/p&gt;
&lt;h2 id="the-bedrock-memory-integration"&gt;
&lt;a href="#the-bedrock-memory-integration" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Bedrock Memory Integration
&lt;/h2&gt;
&lt;p&gt;One thing that &amp;ldquo;just worked&amp;rdquo; was Bedrock Memory. Our configuration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Semantic indexing&lt;/strong&gt;: Auto-retrieve relevant context from past conversations&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Summarization&lt;/strong&gt;: Compress long conversation histories so the context window stays manageable&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Retention&lt;/strong&gt;: 90 days&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Last K turns&lt;/strong&gt;: Load the 10 most recent turns on agent initialization&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We use a &lt;code&gt;HookProvider&lt;/code&gt; pattern (more on hooks below) to load memory on session start. The agent gets conversation context without any explicit retrieval code — Bedrock handles the vector search and summarization behind the scenes.&lt;/p&gt;
&lt;p&gt;This is one of Strands&amp;rsquo; strongest selling points compared to LangGraph: the managed infrastructure handles memory, auth, and tool routing. With LangGraph, you&amp;rsquo;d wire each of these manually.&lt;/p&gt;
&lt;h2 id="the-gotchas-nobody-tells-you-about"&gt;
&lt;a href="#the-gotchas-nobody-tells-you-about" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Gotchas Nobody Tells You About
&lt;/h2&gt;
&lt;p&gt;Building a real agent teaches you things that docs don&amp;rsquo;t.&lt;/p&gt;
&lt;h3 id="container-tool-caching-high-impact"&gt;
&lt;a href="#container-tool-caching-high-impact" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Container Tool Caching (High Impact)
&lt;/h3&gt;
&lt;p&gt;After deploying a new Gateway Lambda function, the running AgentCore container still has &lt;strong&gt;stale tool definitions&lt;/strong&gt;. The MCP client caches tool schemas at container startup. Your only option: wait 60-90 seconds for the container idle timeout to trigger a restart.&lt;/p&gt;
&lt;p&gt;This sounds minor. It isn&amp;rsquo;t. When you&amp;rsquo;re iterating on a Gateway tool — deploy, test, see wrong behavior, realize it&amp;rsquo;s stale, wait, test again — you lose 2-3 minutes per cycle. Over a day of active development, that adds up.&lt;/p&gt;
&lt;h3 id="response-format-quirk"&gt;
&lt;a href="#response-format-quirk" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Response Format Quirk
&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;agent.result.message&lt;/code&gt; returns a dict, not a string:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# What you might expect&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="c1"&gt;# &amp;#34;Here&amp;#39;s the extracted text...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# What you actually get&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="c1"&gt;# {&amp;#34;role&amp;#34;: &amp;#34;assistant&amp;#34;, &amp;#34;content&amp;#34;: [{&amp;#34;text&amp;#34;: &amp;#34;Here&amp;#39;s the extracted text...&amp;#34;}]}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Every caller has to extract &lt;code&gt;content[*].text&lt;/code&gt; blocks. It&amp;rsquo;s a small thing, but when you&amp;rsquo;re debugging at midnight wondering why your response is empty&amp;hellip; you remember this one. Wish the SDK just handled it.&lt;/p&gt;
&lt;h3 id="boto3-and-sdk-compatibility"&gt;
&lt;a href="#boto3-and-sdk-compatibility" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
boto3 and SDK compatibility
&lt;/h3&gt;
&lt;p&gt;The Strands and AgentCore SDK surfaces move independently. Keep &lt;code&gt;boto3&lt;/code&gt;/&lt;code&gt;botocore&lt;/code&gt; and the Strands packages on versions supported by the current AgentCore deployment guide; do not preserve the old &lt;code&gt;boto3 &amp;gt;= 1.42.54&lt;/code&gt; pin from this PoC as a universal requirement. If another service pins an older SDK, isolate the environments and run the current compatibility checks before deployment.&lt;/p&gt;
&lt;h2 id="the-hard-part-the-async-tool-gap"&gt;
&lt;a href="#the-hard-part-the-async-tool-gap" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Hard Part: The Async Tool Gap
&lt;/h2&gt;
&lt;p&gt;Now for the finding that changed my confidence level from &amp;ldquo;high&amp;rdquo; to &amp;ldquo;medium.&amp;rdquo; This is where Strands&amp;rsquo; simplicity becomes a real limitation.&lt;/p&gt;
&lt;h3 id="the-problem"&gt;
&lt;a href="#the-problem" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Problem
&lt;/h3&gt;
&lt;p&gt;Our platform has dozens of backend actions. Roughly half are synchronous. &lt;strong&gt;The other half are asynchronous&lt;/strong&gt; — things like Textract document extraction (2-5 minutes for large PDFs), async LLM inference, document intelligence processing. The async ones use a robust infrastructure: job tracking, Step Functions for polling, EventBridge for completion events.&lt;/p&gt;
&lt;p&gt;But Strands&amp;rsquo; autonomous loop completes in a single conversation turn. When a tool returns &lt;code&gt;{&amp;quot;status&amp;quot;: &amp;quot;pending&amp;quot;}&lt;/code&gt;, the agent receives it, tells the user &amp;ldquo;processing&amp;hellip;&amp;rdquo;, and the conversation ends. There&amp;rsquo;s no built-in mechanism to poll, wait, or resume.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s what actually happens in practice:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;User: &amp;#34;Extract text from this document&amp;#34;
Agent: calls extract_document(&amp;#34;file_id_123&amp;#34;)
→ returns {&amp;#34;status&amp;#34;: &amp;#34;pending&amp;#34;, &amp;#34;job_id&amp;#34;: &amp;#34;abc-def-...&amp;#34;}
Agent: &amp;#34;Text extraction is in progress. Please check back later.&amp;#34;
[Agent loop ends — no mechanism to retry or wait]
User: &amp;#34;Is it done yet?&amp;#34;
Agent: calls check_job_status(&amp;#34;abc-def-...&amp;#34;)
→ returns {&amp;#34;status&amp;#34;: &amp;#34;pending&amp;#34;}
Agent: &amp;#34;Still processing...&amp;#34;
User: [waits another minute, asks again]
Agent: calls check_job_status(&amp;#34;abc-def-...&amp;#34;)
→ returns {&amp;#34;status&amp;#34;: &amp;#34;succeeded&amp;#34;, &amp;#34;output&amp;#34;: &amp;#34;...&amp;#34;}
Agent: &amp;#34;Here&amp;#39;s the extracted text!&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The user has to manually poll. That&amp;rsquo;s not a great experience.&lt;/p&gt;
&lt;h3 id="why-this-matters-more-than-it-seems"&gt;
&lt;a href="#why-this-matters-more-than-it-seems" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why This Matters More Than It Seems
&lt;/h3&gt;
&lt;p&gt;During the early PoC, this felt like a minor issue — we mostly exposed sync tools first. But looking at our actual backend, roughly half of our actions are asynchronous. Things like document processing via Textract, async LLM inference, batch data operations — these are all long-running jobs that return &lt;code&gt;pending&lt;/code&gt; and complete later.&lt;/p&gt;
&lt;p&gt;Most of these aren&amp;rsquo;t exposed to the Strands agent yet. But as we expand the tool surface toward production, this architectural gap becomes central.&lt;/p&gt;
&lt;h3 id="the-architecture-disconnect"&gt;
&lt;a href="#the-architecture-disconnect" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Architecture Disconnect
&lt;/h3&gt;
&lt;p&gt;Our backend already has great async infrastructure. The pattern is common in AWS: when a tool kicks off a long-running job (e.g., Textract, async inference), it returns immediately with &lt;code&gt;{&amp;quot;status&amp;quot;: &amp;quot;pending&amp;quot;}&lt;/code&gt; and a job ID. A Step Functions state machine then polls for completion, updates the job status, and stores the result.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Tool invocation
│
├─ Sync job? → execute → status: &amp;#34;succeeded&amp;#34; → return
│
└─ Async job? → validate → status: &amp;#34;pending&amp;#34;
│
▼
Step Functions
├─ Poll every N seconds
├─ Update status: pending → running → succeeded
└─ Store result
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The problem is that &lt;strong&gt;Strands doesn&amp;rsquo;t know about any of this&lt;/strong&gt;. The agent calls the tool, gets back &lt;code&gt;{&amp;quot;status&amp;quot;: &amp;quot;pending&amp;quot;}&lt;/code&gt;, and moves on. Step Functions completes the job in the background, but the agent is long gone.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;┌─────────────────────────────────┐
│ Strands Agent │
│ call tool() │
│ → {&amp;#34;status&amp;#34;: &amp;#34;pending&amp;#34;} │
│ → tells user &amp;#34;processing...&amp;#34; │
│ → loop ends ← HERE │
└────────────────┬────────────────┘
│ (disconnected)
┌────────────────▼────────────────┐
│ Backend Async Infrastructure │
│ Step Functions polling │
│ Job: pending → succeeded │
│ (agent is already gone) │
└─────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="where-langgraph-would-shine"&gt;
&lt;a href="#where-langgraph-would-shine" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Where LangGraph Would Shine
&lt;/h3&gt;
&lt;p&gt;This is, honestly, where LangGraph&amp;rsquo;s state machine model is genuinely superior. You can define a polling loop as a conditional edge:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;workflow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_conditional_edges&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;poll_status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;route_by_status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;check_again&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;poll_status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;# ← loops back&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;completed&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;summarize&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;timeout&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;END&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The state machine maintains &lt;code&gt;job_id&lt;/code&gt;, &lt;code&gt;retries&lt;/code&gt;, &lt;code&gt;status&lt;/code&gt; across iterations. One user request, one final result. No manual polling.&lt;/p&gt;
&lt;h3 id="our-workaround-strategy"&gt;
&lt;a href="#our-workaround-strategy" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Our Workaround Strategy
&lt;/h3&gt;
&lt;p&gt;We&amp;rsquo;re not switching frameworks over this. Instead, we&amp;rsquo;re taking a phased approach:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Right now&lt;/strong&gt;: Accept manual polling for the few async tools we&amp;rsquo;ve exposed. Not ideal, but the frequency is low enough that users tolerate it.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Next quarter&lt;/strong&gt;: Implement EventBridge + Bedrock Memory integration. When an async job completes, an EventBridge rule triggers a Lambda that writes the result to Bedrock Memory with a tagged key. When the user asks again, the agent checks Memory first — one follow-up instead of repeated polling.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;extract_document_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;Start async extraction. Results stored in memory on completion.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;job_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;start_textract_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# EventBridge: Textract complete → Lambda → save to Memory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;processing&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;message&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Extraction started. Ask me again in a minute for results.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;check_extraction_result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;Check if extraction completed (reads from Memory first).&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;memory_client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;search&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tag&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;extraction_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;completed&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;dumps&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;status&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;check_job_status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Before production&lt;/strong&gt;: If async tool usage looks like it&amp;rsquo;ll exceed 60-70% of traffic, we&amp;rsquo;ll re-evaluate LangGraph. By then, the runtime and framework versions may have changed, so repeat the image-build and lifecycle measurements instead of relying on the historical 30-second observation.&lt;/p&gt;
&lt;h2 id="the-numbers"&gt;
&lt;a href="#the-numbers" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Numbers
&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s where the metrics stood in the February 2026 PoC:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Metric&lt;/th&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Agent setup code&lt;/td&gt;
&lt;td&gt;26 lines (agent.py) + 79 lines (main.py)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lines per tool (avg)&lt;/td&gt;
&lt;td&gt;~24 lines with &lt;code&gt;@tool&lt;/code&gt; decorator&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tools implemented&lt;/td&gt;
&lt;td&gt;25 (15 direct + 10 Gateway)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cold start&lt;/td&gt;
&lt;td&gt;&amp;lt; 500ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Full CDK deploy&lt;/td&gt;
&lt;td&gt;~8-12 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Container tool refresh&lt;/td&gt;
&lt;td&gt;~60-90 sec after Gateway update&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code reduction vs LangGraph&lt;/td&gt;
&lt;td&gt;~3× for tool definitions&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;These are workload-specific measurements, not current service-level objectives. Re-run them after changing the runtime image, region, model, or framework version.&lt;/p&gt;
&lt;h2 id="so-which-one-should-you-pick"&gt;
&lt;a href="#so-which-one-should-you-pick" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
So, Which One Should You Pick?
&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re deploying on Bedrock AgentCore with mostly synchronous tools, &lt;strong&gt;Strands is the clear winner&lt;/strong&gt;. The developer experience is outstanding — 26 lines for an agent, sub-500ms cold starts, and &lt;code&gt;@tool&lt;/code&gt; decorator that eliminates boilerplate. The managed infrastructure (memory, auth, tool routing) saves weeks of wiring.&lt;/p&gt;
&lt;p&gt;If your workload is async-heavy — lots of long-running jobs, polling, multi-step workflows with state — &lt;strong&gt;LangGraph&amp;rsquo;s state machine model is architecturally better suited&lt;/strong&gt;. But you&amp;rsquo;ll need to solve the AgentCore deployment friction first (Docker pre-compilation, dependency bundling).&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re somewhere in the middle like us — mostly sync today, growing async tomorrow — &lt;strong&gt;start with Strands and plan your async workarounds early&lt;/strong&gt;. Don&amp;rsquo;t wait until half your tools are async to discover the gap like we did.&lt;/p&gt;
&lt;p&gt;The honest answer is that there&amp;rsquo;s no perfect framework yet for this space. Strands trades control for simplicity. LangGraph trades simplicity for control. Both trade-offs have consequences that only show up after you&amp;rsquo;ve been building with them for a while.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re building agentic workflows on AWS and have questions about any of this, feel free to reach out. I&amp;rsquo;m still learning too, and the landscape is changing fast. Hopefully this post saves you some of that discovery time we had to go through the hard way.&lt;/p&gt;</description></item><item><title>Why You Should Switch to uv for Python Projects</title><link>https://www.yopa.page/blog/2026-02-14-why-you-should-switch-to-uv.html</link><pubDate>Sat, 14 Feb 2026 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-02-14-why-you-should-switch-to-uv.html</guid><description>
&lt;p&gt;If you are a Python developer, you are likely familiar with the fragmented ecosystem of tooling. You use &lt;code&gt;pip&lt;/code&gt; for installing packages, &lt;code&gt;venv&lt;/code&gt; or &lt;code&gt;virtualenv&lt;/code&gt; for environments, &lt;code&gt;pyenv&lt;/code&gt; for managing Python versions, and maybe &lt;code&gt;poetry&lt;/code&gt; or &lt;code&gt;pip-tools&lt;/code&gt; for dependency resolution. It’s a lot to manage.&lt;/p&gt;
&lt;p&gt;Enter &lt;strong&gt;uv&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Created by Astral (the team behind Ruff), &lt;code&gt;uv&lt;/code&gt; is an extremely fast Python package and project manager written in Rust. It’s designed to be a drop-in replacement for &lt;code&gt;pip&lt;/code&gt; and &lt;code&gt;pip-tools&lt;/code&gt;, but it has evolved into a full-fledged project manager that challenges &lt;code&gt;poetry&lt;/code&gt; and &lt;code&gt;pdm&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In this post, I’ll explain why &lt;code&gt;uv&lt;/code&gt; is a game-changer and why you should consider switching today.&lt;/p&gt;
&lt;h2 id="why-uv-is-a-game-changer"&gt;
&lt;a href="#why-uv-is-a-game-changer" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why &lt;code&gt;uv&lt;/code&gt; is a Game Changer
&lt;/h2&gt;
&lt;h3 id="1-blazing-speed"&gt;
&lt;a href="#1-blazing-speed" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1. Blazing Speed
&lt;/h3&gt;
&lt;p&gt;Yup, it is freaking fast. In many benchmarks, &lt;code&gt;uv&lt;/code&gt; is &lt;strong&gt;10-100x faster&lt;/strong&gt; than pip-tools or poetry for resolution.&lt;/p&gt;
&lt;h3 id="2-unified-toolchain"&gt;
&lt;a href="#2-unified-toolchain" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2. Unified Toolchain
&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;uv&lt;/code&gt; consolidates multiple tools into one. You no longer need separate tools for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Package Installation&lt;/strong&gt; (&lt;code&gt;pip&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Environment Management&lt;/strong&gt; (&lt;code&gt;venv&lt;/code&gt;, &lt;code&gt;virtualenv&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Python Version Management&lt;/strong&gt; (&lt;code&gt;pyenv&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dependency Locking&lt;/strong&gt; (&lt;code&gt;pip-compile&lt;/code&gt;, &lt;code&gt;poetry lock&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Script Running&lt;/strong&gt; (&lt;code&gt;pipx&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You can do it all with a single binary.&lt;/p&gt;
&lt;h3 id="3-automatic-python-version-management"&gt;
&lt;a href="#3-automatic-python-version-management" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
3. Automatic Python Version Management
&lt;/h3&gt;
&lt;p&gt;This is my favorite feature. If a project requires Python 3.12 but you only have 3.11 installed, tools like &lt;code&gt;poetry&lt;/code&gt; will complain. You’d have to go to &lt;code&gt;pyenv&lt;/code&gt;, install 3.12, and then tell poetry to use it.&lt;/p&gt;
&lt;p&gt;With &lt;code&gt;uv&lt;/code&gt;, you just define the required python version in your project. &lt;code&gt;uv&lt;/code&gt; will &lt;strong&gt;automatically download and install&lt;/strong&gt; the correct Python version for that project if it&amp;rsquo;s missing, in an isolated manner (in &lt;code&gt;.venv&lt;/code&gt;). No more global Python version conflicts!!&lt;/p&gt;
&lt;h2 id="comparison-uv-vs-poetry"&gt;
&lt;a href="#comparison-uv-vs-poetry" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Comparison: uv vs Poetry
&lt;/h2&gt;
&lt;p&gt;If you are currently using Poetry, you might wonder if it’s worth the switch.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th style="text-align: left"&gt;Feature&lt;/th&gt;
&lt;th style="text-align: left"&gt;Poetry&lt;/th&gt;
&lt;th style="text-align: left"&gt;uv&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;Language&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;Python&lt;/td&gt;
&lt;td style="text-align: left"&gt;Rust&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;Speed&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;Slow (dependency resolution can take minutes)&lt;/td&gt;
&lt;td style="text-align: left"&gt;Instant (sub-second resolution)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;Python Management&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;Relies on external tools (pyenv/system)&lt;/td&gt;
&lt;td style="text-align: left"&gt;Built-in (installs Python automatically)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;Standards&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;Uses &lt;code&gt;pyproject.toml&lt;/code&gt; (standard)&lt;/td&gt;
&lt;td style="text-align: left"&gt;Uses &lt;code&gt;pyproject.toml&lt;/code&gt; (standard)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style="text-align: left"&gt;&lt;strong&gt;Lock File&lt;/strong&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;code&gt;poetry.lock&lt;/code&gt;&lt;/td&gt;
&lt;td style="text-align: left"&gt;&lt;code&gt;uv.lock&lt;/code&gt; (cross-platform)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The biggest pain point with Poetry has always been the dependency resolver speed. &lt;code&gt;uv&lt;/code&gt; solves this completely.&lt;/p&gt;
&lt;h2 id="migrating-from-requirementstxt"&gt;
&lt;a href="#migrating-from-requirementstxt" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migrating from &lt;code&gt;requirements.txt&lt;/code&gt;
&lt;/h2&gt;
&lt;p&gt;If you have a legacy project using &lt;code&gt;requirements.txt&lt;/code&gt;, migrating to &lt;code&gt;uv&lt;/code&gt; is EZ. You don&amp;rsquo;t have to rewrite everything from scratch.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;uv&lt;/code&gt; supports installing directly from requirements files, and it gives you two ways to do it:&lt;/p&gt;
&lt;h3 id="1-using-uv-pip-the-pip-way"&gt;
&lt;a href="#1-using-uv-pip-the-pip-way" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1. Using &lt;code&gt;uv pip&lt;/code&gt; (The &amp;ldquo;pip&amp;rdquo; way)
&lt;/h3&gt;
&lt;p&gt;If you just want to use &lt;code&gt;uv&lt;/code&gt; as a faster &lt;code&gt;pip&lt;/code&gt; without changing your workflow:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uv pip install -r requirements.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This installs packages into your current environment exactly like &lt;code&gt;pip install -r requirements.txt&lt;/code&gt;, just much faster.&lt;/p&gt;
&lt;h3 id="2-using-uv-add-the-project-way"&gt;
&lt;a href="#2-using-uv-add-the-project-way" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2. Using &lt;code&gt;uv add&lt;/code&gt; (The &amp;ldquo;Project&amp;rdquo; way)
&lt;/h3&gt;
&lt;p&gt;If you are initializing a new &lt;code&gt;uv&lt;/code&gt; project (&lt;code&gt;uv init&lt;/code&gt;) and want to import your dependencies:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uv add -r requirements.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This reads your &lt;code&gt;requirements.txt&lt;/code&gt; and adds the dependencies to your &lt;code&gt;pyproject.toml&lt;/code&gt; file, effectively migrating your project management to &lt;code&gt;uv&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="common-commands-cheat-sheet"&gt;
&lt;a href="#common-commands-cheat-sheet" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Common Commands Cheat Sheet
&lt;/h2&gt;
&lt;p&gt;Here are the commands you will use 90% of the time:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Initialize a project&lt;/strong&gt;:
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uv init
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Add a dependency&lt;/strong&gt;:
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uv add requests
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Run a script&lt;/strong&gt;:
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uv run main.py
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;em&gt;Note: &lt;code&gt;uv run&lt;/code&gt; will automatically create a virtual environment, install dependencies, and run the script in that isolated environment.&lt;/em&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sync environment&lt;/strong&gt;:
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;uv sync
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;em&gt;Ensures the virtual environment matches the &lt;code&gt;uv.lock&lt;/code&gt; file.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="uv-add-vs-uv-pip-install"&gt;
&lt;a href="#uv-add-vs-uv-pip-install" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;code&gt;uv add&lt;/code&gt; vs &lt;code&gt;uv pip install&lt;/code&gt;
&lt;/h2&gt;
&lt;p&gt;It’s important to understand the difference between these two commands, as &lt;code&gt;uv&lt;/code&gt; supports both &amp;ldquo;project&amp;rdquo; and &amp;ldquo;script&amp;rdquo; workflows.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;uv add &amp;lt;package&amp;gt;&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Used for managing a &lt;strong&gt;project&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Updates &lt;code&gt;pyproject.toml&lt;/code&gt; and &lt;code&gt;uv.lock&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Ensures reproducible builds for your application.&lt;/li&gt;
&lt;li&gt;Similar to &lt;code&gt;poetry add&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;uv pip install &amp;lt;package&amp;gt;&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Used for low-level environment modification.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Does NOT&lt;/strong&gt; update &lt;code&gt;pyproject.toml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Useful for CI/CD pipelines or &lt;strong&gt;ad-hoc&lt;/strong&gt; environments where you just need packages installed quickly without tracking them.&lt;/li&gt;
&lt;li&gt;Similar to &lt;code&gt;pip install&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="caveats"&gt;
&lt;a href="#caveats" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Caveats
&lt;/h2&gt;
&lt;p&gt;While &lt;code&gt;uv&lt;/code&gt; is fantastic, it is moving very fast.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It is relatively new, so edge cases with obscure build backends might still exist.&lt;/li&gt;
&lt;li&gt;If you rely heavily on Poetry plugins, &lt;code&gt;uv&lt;/code&gt; might not have an equivalent yet.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;Give it a try on your next project. You won’t want to go back. At least I won&amp;rsquo;t.
Cheers!&lt;/p&gt;</description></item><item><title>Python __init__.py Explained: Mastering Package Structure and Imports</title><link>https://www.yopa.page/blog/2026-02-09-demystifying-init-py.html</link><pubDate>Mon, 09 Feb 2026 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-02-09-demystifying-init-py.html</guid><description>
&lt;p&gt;If you’ve ever dug into a popular Python library like &lt;code&gt;requests&lt;/code&gt; or &lt;code&gt;pandas&lt;/code&gt;, you’ve likely stumbled upon a file named &lt;code&gt;__init__.py&lt;/code&gt;. Sometimes it’s empty, sometimes it’s packed with imports, but it’s almost always there.&lt;/p&gt;
&lt;p&gt;When I first started with Python, I treated &lt;code&gt;__init__.py&lt;/code&gt; as a magical ritual—a file I &lt;em&gt;had&lt;/em&gt; to create to make my imports work, without really understanding why.&lt;/p&gt;
&lt;p&gt;Turns out, &lt;code&gt;__init__.py&lt;/code&gt; isn’t just boilerplate. It’s the &lt;strong&gt;gateway to your package&lt;/strong&gt;. It allows you to transform a messy directory of scripts into a easy-to-use library.&lt;/p&gt;
&lt;p&gt;Let’s look at a scenario to see how we can use &lt;code&gt;__init__.py&lt;/code&gt; to refactor a messy project into something more professional that you love to show off.&lt;/p&gt;
&lt;h3 id="scenario-building-a-notification-system"&gt;
&lt;a href="#scenario-building-a-notification-system" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Scenario: Building a Notification System
&lt;/h3&gt;
&lt;p&gt;Imagine you&amp;rsquo;re building a backend service that needs to send notifications via different channels: Email, SMS, and Push Notifications.&lt;/p&gt;
&lt;p&gt;A typical beginner&amp;rsquo;s project structure might look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;notifications/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; __init__.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; email_service.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; sms_service.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; push_service.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;main.py
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Inside &lt;code&gt;email_service.py&lt;/code&gt;, you might have:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# notifications/email_service.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;EmailSender&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Sending Email: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;And similarly for SMS and Push.&lt;/p&gt;
&lt;h3 id="the-problem-clunky-deep-imports"&gt;
&lt;a href="#the-problem-clunky-deep-imports" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Problem: Clunky, &amp;ldquo;Deep&amp;rdquo; Imports
&lt;/h3&gt;
&lt;p&gt;Now, when you want to use these classes in your &lt;code&gt;main.py&lt;/code&gt;, you must know exactly which file contains which class. Your imports become long and expose internal file structure:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# main.py (Without proper __init__.py usage)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;notifications.email_service&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;EmailSender&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;notifications.sms_service&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SmsSender&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;notifications.push_service&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PushSender&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;notify_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Usage is fine, but the imports are ugly&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;sender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EmailSender&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;I mean it is okay, but it’s annoying.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Verbose:&lt;/strong&gt; You have to type &lt;code&gt;notifications.filename&lt;/code&gt; every time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Brittle:&lt;/strong&gt; If you rename &lt;code&gt;email_service.py&lt;/code&gt; to &lt;code&gt;email.py&lt;/code&gt; later, you break every file that imports it.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="the-solution-expose-classes-in-__init__py"&gt;
&lt;a href="#the-solution-expose-classes-in-__init__py" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Solution: Expose Classes in &lt;code&gt;__init__.py&lt;/code&gt;
&lt;/h3&gt;
&lt;p&gt;We can use &lt;code&gt;__init__.py&lt;/code&gt; to create a shortcut.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s modify &lt;code&gt;notifications/__init__.py&lt;/code&gt; to import the classes &lt;em&gt;inside&lt;/em&gt; the package, so they are available at the top level.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# notifications/__init__.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Use relative import (.) to import from the current directory&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.email_service&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;EmailSender&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.sms_service&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;SmsSender&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;.push_service&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;PushSender&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Optional: Control what gets imported with &amp;#39;from notifications import *&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;__all__&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;EmailSender&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;SmsSender&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;PushSender&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="the-result-clean-flat-imports"&gt;
&lt;a href="#the-result-clean-flat-imports" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Result: Clean, &amp;ldquo;Flat&amp;rdquo; Imports
&lt;/h3&gt;
&lt;p&gt;Now, look at how beautiful and intuitive &lt;code&gt;main.py&lt;/code&gt; becomes. The user doesn&amp;rsquo;t need to know about &lt;code&gt;email_service.py&lt;/code&gt; or &lt;code&gt;sms_service.py&lt;/code&gt;. They just import from &lt;code&gt;notifications&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# main.py (With proper __init__.py usage)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# CLEAN! No need to know internal file names&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;notifications&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;EmailSender&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SmsSender&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;notify_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;sender&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;EmailSender&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Why is this better?&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Abstraction:&lt;/strong&gt; The user of your package doesn&amp;rsquo;t need to know that &lt;code&gt;EmailSender&lt;/code&gt; lives in a file called &lt;code&gt;email_service.py&lt;/code&gt;. It just lives in the &lt;code&gt;notifications&lt;/code&gt; package.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Refactoring Safety:&lt;/strong&gt; You can move &lt;code&gt;EmailSender&lt;/code&gt; to &lt;code&gt;backend/legacy/email.py&lt;/code&gt; later, and as long as you update the import in &lt;code&gt;__init__.py&lt;/code&gt;, your users won&amp;rsquo;t notice a thing. Their code doesn&amp;rsquo;t break.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="advanced-tip-lazy-loading"&gt;
&lt;a href="#advanced-tip-lazy-loading" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Advanced Tip: Lazy Loading
&lt;/h2&gt;
&lt;p&gt;One downside of importing everything in &lt;code&gt;__init__.py&lt;/code&gt; is that it loads &lt;em&gt;all&lt;/em&gt; modules when you import the package. If &lt;code&gt;PushSender&lt;/code&gt; requires a heavy library (like a specific SDK) that takes time to load, it might slow down your app even if you only wanted to send an SMS.&lt;/p&gt;
&lt;p&gt;You can get fancy with &amp;ldquo;Lazy Loading&amp;rdquo; inside &lt;code&gt;__init__.py&lt;/code&gt; if performance is critical using &lt;code&gt;__getattr__&lt;/code&gt;, but for 99% of projects, the pattern above is the gold standard.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;__init__.py&lt;/code&gt; file is a tool for &lt;strong&gt;encapsulation&lt;/strong&gt;. It lets you hide the messy details of your file structure and present a clean, flat interface to the world.&lt;/p&gt;
&lt;p&gt;Next time you create a folder in Python, don&amp;rsquo;t just leave &lt;code&gt;__init__.py&lt;/code&gt; empty. Ask yourself: &lt;em&gt;&amp;ldquo;How do I want other developers to import my code?&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Cheers!&lt;/p&gt;</description></item><item><title>Why Do Python Experts Use the @property Decorator?</title><link>https://www.yopa.page/blog/2026-02-09-why-do-python-experts-use-the-property-decorator.html</link><pubDate>Mon, 09 Feb 2026 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-02-09-why-do-python-experts-use-the-property-decorator.html</guid><description>
&lt;p&gt;If you are learning Python, you might have come across the &lt;code&gt;@property&lt;/code&gt; decorator. While many decorators can seem complex to beginners, &lt;code&gt;@property&lt;/code&gt; is often confusing for the opposite reason: its behavior is so simple—it allows you to declare a function but access it like a variable—that many wonder why it is necessary at all.&lt;/p&gt;
&lt;p&gt;I recently watched a video by &lt;a href="https://www.youtube.com/@LimCommit"&gt;임커밋&lt;/a&gt; that highlighted the importance of &lt;code&gt;@property&lt;/code&gt; not just as syntax sugar, but as a tool for better architectural control. Inspired by that, I wanted to share my own take on why experienced developers lean on this feature.&lt;/p&gt;
&lt;p&gt;Here are the two main reasons to use &lt;code&gt;@property&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="1-dynamic-attributes-computed-properties"&gt;
&lt;a href="#1-dynamic-attributes-computed-properties" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1. Dynamic Attributes (Computed Properties)
&lt;/h2&gt;
&lt;p&gt;The most common use case arises when the external &amp;ldquo;look&amp;rdquo; of a value should be a simple attribute, but the internal logic requires calculation.&lt;/p&gt;
&lt;p&gt;Consider an e-commerce &lt;code&gt;ShoppingCart&lt;/code&gt; class. A cart contains a list of items, each with a price. Naturally, you want to know the total price.&lt;/p&gt;
&lt;h3 id="the-problem"&gt;
&lt;a href="#the-problem" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Problem
&lt;/h3&gt;
&lt;p&gt;If you define &lt;code&gt;total&lt;/code&gt; as a standard instance variable, you must remember to update it every time an item is added or removed.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShoppingCart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="c1"&gt;# Needs manual updates!&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt; &lt;span class="c1"&gt;# Easy to forget or get wrong in complex logic&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;If a developer modifies &lt;code&gt;items&lt;/code&gt; directly or forgets to update &lt;code&gt;total&lt;/code&gt;, the data becomes inconsistent!&lt;/p&gt;
&lt;h3 id="the-function-solution"&gt;
&lt;a href="#the-function-solution" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Function Solution
&lt;/h3&gt;
&lt;p&gt;You could write a method like &lt;code&gt;get_total()&lt;/code&gt;, but accessing it requires parentheses (e.g., &lt;code&gt;cart.get_total()&lt;/code&gt;). This works, but it exposes the implementation detail that &lt;code&gt;total&lt;/code&gt; is calculated, rather than just being a property of the cart. and we all know less code is better. no code no bug. lol&lt;/p&gt;
&lt;h3 id="the-property-solution"&gt;
&lt;a href="#the-property-solution" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The @property Solution
&lt;/h3&gt;
&lt;p&gt;By using &lt;code&gt;@property&lt;/code&gt;, you can define &lt;code&gt;total&lt;/code&gt; as a method internally so it calculates the value fresh every time it is called, but you access it externally like a static attribute (e.g., &lt;code&gt;cart.total&lt;/code&gt;).&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ShoppingCart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nd"&gt;@property&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;total&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;cart&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ShoppingCart&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_item&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cart&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;total&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 150 (Computed on the fly!)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This ensures your data is always consistent—the total can never be out of sync with the items list—without sacrificing attribute-like syntax.&lt;/p&gt;
&lt;h2 id="2-read-only-protection"&gt;
&lt;a href="#2-read-only-protection" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2. Read-Only Protection
&lt;/h2&gt;
&lt;p&gt;The second major reason to use &lt;code&gt;@property&lt;/code&gt; is to create &amp;ldquo;read-only&amp;rdquo; attributes.&lt;/p&gt;
&lt;p&gt;Sometimes you have data that should be visible to the outside world but never modified directly. For example, a &lt;code&gt;User&lt;/code&gt; class might have a unique &lt;code&gt;id&lt;/code&gt; assigned at creation.&lt;/p&gt;
&lt;p&gt;If &lt;code&gt;id&lt;/code&gt; were a normal attribute, nothing stops a developer from accidentally overwriting it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;123&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;456&lt;/span&gt; &lt;span class="c1"&gt;# Oops! Data integrity broken.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;However, when you define a value using &lt;code&gt;@property&lt;/code&gt; without a corresponding setter method, it becomes read-only by default.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;username&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;username&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nd"&gt;@property&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_user_id&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;User&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;python_guru&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 101&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Attempting to modify it raises an error&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;999&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="ne"&gt;AttributeError&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# can&amp;#39;t set attribute&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This behavior allows developers to protect sensitive or critical data (like IDs, timestamps, or derived values) from being accidentally overwritten by external code. It effectively isolates the internal logic from external interference.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;While it might seem like a small syntactic sugar, the &lt;code&gt;@property&lt;/code&gt; decorator is a something you need to consider for encapsulation. It allows you to maintain the clean interface of simple attributes while keeping the safety of dynamic functions in the background.&lt;/p&gt;</description></item><item><title>Build a Governed Agent Platform on AWS</title><link>https://www.yopa.page/blog/2026-08-01-build-a-governed-agent-platform-on-aws.html</link><pubDate>Sat, 20 Dec 2025 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-build-a-governed-agent-platform-on-aws.html</guid><description>
&lt;p&gt;An “agent factory” should not mean giving a model broad IAM permissions and asking it to create roles, repositories, images, and runtimes directly. Natural language can propose an agent, but production infrastructure needs a deterministic, reviewable control plane.&lt;/p&gt;
&lt;p&gt;The safer pattern is a governed internal platform: an agent specification becomes a pull request or signed request, policy and evaluation gates produce evidence, and a deployment service—not the agent being built—applies an approved template.&lt;/p&gt;
&lt;h2 id="define-the-product-contract"&gt;
&lt;a href="#define-the-product-contract" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Define the product contract
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;order-status-assistant&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;owner&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;commerce-platform&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;protocol&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;HTTP&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;model_policy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;approved-low-risk&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;get_order_status&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;data_classification&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;internal&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;enabled&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;network&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;internet_egress&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;human_gate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;required_for_write_tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;budgets&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;max_runtime_seconds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;120&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;max_tool_calls&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Validate this schema and reject unknown fields. Resolve tool names through an approved catalog whose entries include owner, input/output schema, authorization model, data class, availability, and test endpoint. A prompt cannot request an arbitrary role ARN or container image.&lt;/p&gt;
&lt;h2 id="separate-planes-and-identities"&gt;
&lt;a href="#separate-planes-and-identities" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Separate planes and identities
&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;platform control plane&lt;/strong&gt; validates specs, synthesizes CDK/CloudFormation/Terraform, runs policy checks, builds images, creates an immutable release, and deploys AgentCore resources. Its privileged actions run under a tightly controlled CI/deployment role with approvals and audit.&lt;/p&gt;
&lt;p&gt;The &lt;strong&gt;agent data plane&lt;/strong&gt; handles invocations with a per-agent execution role. It cannot create IAM roles, alter its runtime, publish images, or grant itself tools. AgentCore Identity/Gateway may provide outbound credentials and tool access, but each target still authorizes the actual action.&lt;/p&gt;
&lt;p&gt;Use separate roles for source checkout, image build, infrastructure deployment, runtime execution, and break-glass recovery. External CI should assume roles through OIDC. Never use &lt;code&gt;aws configure&lt;/code&gt; or a developer&amp;rsquo;s static key in automation.&lt;/p&gt;
&lt;h2 id="supply-chain-controls"&gt;
&lt;a href="#supply-chain-controls" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Supply-chain controls
&lt;/h2&gt;
&lt;p&gt;Build from a pinned base image and lock dependencies. Generate an SBOM, scan dependencies and image, sign the image, store it in private ECR with immutable tags, and deploy by digest. Record source commit, build identity, evaluation result, policy version, image digest, model configuration, and infrastructure plan in release provenance.&lt;/p&gt;
&lt;p&gt;Templates should provide secure defaults: log redaction, encryption, private networking where needed, bounded egress, resource tags, deletion/retention, lifecycle settings, alarms, and budgets. Exceptions require an owner, rationale, expiry, and additional test.&lt;/p&gt;
&lt;h2 id="evaluation-gates"&gt;
&lt;a href="#evaluation-gates" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Evaluation gates
&lt;/h2&gt;
&lt;p&gt;Run deterministic application tests before model evaluation. Then test representative and adversarial cases for task success, tool argument validity, authorization, prompt injection, unsafe actions, data leakage, refusal/escalation, latency, and cost. Compare against a simple baseline; do not approve a multi-agent topology if it adds expense without improving outcomes.&lt;/p&gt;
&lt;p&gt;Write tools need sandbox or draft-mode tests and idempotency. Cross-tenant systems require negative isolation tests. Browser or code execution requires network and artifact controls. A human reviewer approves risk-sensitive releases with the evidence attached, not a generated summary alone.&lt;/p&gt;
&lt;h2 id="deployment-and-operations"&gt;
&lt;a href="#deployment-and-operations" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Deployment and operations
&lt;/h2&gt;
&lt;p&gt;Promote the same immutable artifact from development to staging and production with environment-specific configuration. Start with shadow/read-only traffic, then a small canary. Monitor task success, human correction, tool denials, runtime errors, latency, token/tool cost, queue/backlog if applicable, and anomalous identity use.&lt;/p&gt;
&lt;p&gt;Rollback switches traffic to the previous compatible artifact and configuration. Memory/schema/tool-contract changes need forward/backward compatibility or an explicit migration restore plan. Provide a kill switch that revokes invocation or tool authority independently of a full deployment.&lt;/p&gt;
&lt;h2 id="tradeoffs"&gt;
&lt;a href="#tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Tradeoffs
&lt;/h2&gt;
&lt;p&gt;A platform accelerates many teams and centralizes controls, but it can become a bottleneck or lowest-common-denominator abstraction. Start with two or three repeated agent patterns, not a universal factory. Direct team-owned deployment may be better for an early experiment; require the same identity, test, and cost boundaries before granting production authority.&lt;/p&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Remove infrastructure mutation permission from builder agents.&lt;/li&gt;
&lt;li&gt;Convert desired behavior to a versioned, validated spec.&lt;/li&gt;
&lt;li&gt;Create approved runtime and tool templates with narrow roles.&lt;/li&gt;
&lt;li&gt;Build/sign/scan immutable artifacts in CI and review the infrastructure plan.&lt;/li&gt;
&lt;li&gt;Add deterministic, model, security, isolation, cost, and recovery gates.&lt;/li&gt;
&lt;li&gt;Separate release approval from runtime identity.&lt;/li&gt;
&lt;li&gt;Add canary, provenance, alarms, budgets, kill switch, and rollback.&lt;/li&gt;
&lt;li&gt;Pilot one low-risk agent and measure platform lead time and escaped defects.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Verified on &lt;strong&gt;2026-08-01&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="primary-sources"&gt;
&lt;a href="#primary-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Primary sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime.html"&gt;AgentCore Runtime&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonECR/latest/userguide/image-tag-mutability.html"&gt;Amazon ECR image tag immutability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/signer/latest/developerguide/Welcome.html"&gt;AWS Signer container image signing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://csrc.nist.gov/Projects/ssdf"&gt;NIST Secure Software Development Framework&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Zero-Trust Agent Systems on AWS</title><link>https://www.yopa.page/blog/2026-08-01-zero-trust-agent-systems-on-aws.html</link><pubDate>Wed, 17 Dec 2025 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-zero-trust-agent-systems-on-aws.html</guid><description>
&lt;p&gt;An agent calling another agent is a distributed-system request, not an implicit continuation of user trust. Each hop needs an authenticated workload, an explicit representation of user delegation where required, authorization at the resource, and an auditable decision.&lt;/p&gt;
&lt;p&gt;This is especially important in finance, healthcare, and other consequential systems, but compliance labels do not determine the architecture by themselves. Build from the actual data, action, threat, and regulatory requirements.&lt;/p&gt;
&lt;h2 id="separate-the-identities"&gt;
&lt;a href="#separate-the-identities" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Separate the identities
&lt;/h2&gt;
&lt;p&gt;There are at least three principals:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;the &lt;strong&gt;end user&lt;/strong&gt; requesting an outcome;&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;agent workload&lt;/strong&gt; executing code;&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;target tool/service&lt;/strong&gt; enforcing access.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AgentCore Runtime supports IAM SigV4 inbound authentication or configured JWT authorization. Those are modes, not two headers that every call automatically combines. AgentCore Identity workload identities can broker outbound credentials. If downstream authorization needs the user&amp;rsquo;s authority, propagate a verifiable, audience-restricted delegation token or a trusted user identifier through a documented flow. Never trust &lt;code&gt;tenantId: Alice&lt;/code&gt; inside model-generated JSON.&lt;/p&gt;
&lt;p&gt;At every tool call, authorize the workload and, where applicable, the delegated user against the specific action and resource. Keep the target service&amp;rsquo;s authorization even when Gateway or an interceptor performs an earlier policy check. This is defense in depth and prevents a routing mistake from becoming a data breach.&lt;/p&gt;
&lt;h2 id="minimize-authority"&gt;
&lt;a href="#minimize-authority" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Minimize authority
&lt;/h2&gt;
&lt;p&gt;Expose &lt;code&gt;get_account_balance(account_id)&lt;/code&gt; rather than database credentials or arbitrary SQL. Validate that the account belongs to the authorized subject. Use separate read and write tools, transaction limits, idempotency keys, and human approval for irreversible or regulated actions. A supervisor agent should not pass its broader execution role to a specialist.&lt;/p&gt;
&lt;p&gt;Use per-component IAM roles and constrain trust policies. Protect OAuth/API-key credentials in AgentCore Identity or an approved secret system; never place them in prompts or memory. Rotate and revoke credentials independently of model deployment.&lt;/p&gt;
&lt;h2 id="network-boundaries"&gt;
&lt;a href="#network-boundaries" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Network boundaries
&lt;/h2&gt;
&lt;p&gt;AWS PrivateLink interface endpoints provide private connectivity from a VPC to supported AgentCore service APIs. VPC configuration can also let Runtime and tools reach private resources, with VPC Lattice used for supported connectivity patterns. This does not mean AgentCore&amp;rsquo;s managed control plane and every component “run inside your VPC.” Document inbound endpoint traffic, Runtime/tool egress, DNS, security groups, NAT, and third-party destinations separately.&lt;/p&gt;
&lt;p&gt;A private path does not authorize a request. Keep TLS, IAM/JWT checks, application authorization, and data-level controls. Browser tools that need the public internet require an approved egress path and stronger content isolation.&lt;/p&gt;
&lt;h2 id="memory-and-data"&gt;
&lt;a href="#memory-and-data" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Memory and data
&lt;/h2&gt;
&lt;p&gt;Scope AgentCore Memory by actor and session and design long-term namespaces, but enforce tenant access in application/IAM policy as supported. Classify what may enter prompts, traces, raw events, extracted memories, and tool logs. Define retention, deletion, legal hold, encryption keys, residency, and redaction. Test that one tenant cannot retrieve another tenant&amp;rsquo;s records even with guessed identifiers.&lt;/p&gt;
&lt;h2 id="threats-and-evaluation"&gt;
&lt;a href="#threats-and-evaluation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Threats and evaluation
&lt;/h2&gt;
&lt;p&gt;Include prompt injection, confused deputy, excessive agency, token replay, cross-tenant identifiers, malicious tool output, data exfiltration, duplicate financial action, unavailable policy service, and compromised agent in the threat model.&lt;/p&gt;
&lt;p&gt;Negative tests should prove:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;invalid/expired/audience-mismatched tokens fail;&lt;/li&gt;
&lt;li&gt;an allowed workload cannot exceed the delegated user&amp;rsquo;s scope;&lt;/li&gt;
&lt;li&gt;read-only agents cannot reach write tools;&lt;/li&gt;
&lt;li&gt;repeated requests produce one business effect;&lt;/li&gt;
&lt;li&gt;interceptor/policy failure closes access;&lt;/li&gt;
&lt;li&gt;private endpoints do not create unintended public egress;&lt;/li&gt;
&lt;li&gt;logs and traces contain no secrets or prohibited financial data.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="rollout-and-response"&gt;
&lt;a href="#rollout-and-response" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Rollout and response
&lt;/h2&gt;
&lt;p&gt;Start read-only with synthetic data, then draft actions, then human-approved low-value actions. Define per-user and per-agent budgets, rate limits, anomaly alarms, and a kill switch that disables tool authority without waiting for a model redeploy. Preserve immutable audit records linking user, workload, policy decision, tool, request ID, and result.&lt;/p&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Replace “automatic identity propagation” assumptions with a sequence diagram of real tokens and validators.&lt;/li&gt;
&lt;li&gt;Select SigV4 or JWT inbound auth per caller and document outbound credential exchange.&lt;/li&gt;
&lt;li&gt;Add target-side authorization and narrow tools.&lt;/li&gt;
&lt;li&gt;Map PrivateLink inbound, VPC egress, Lattice, NAT, and public dependencies accurately.&lt;/li&gt;
&lt;li&gt;Add cross-tenant, replay, idempotency, and fail-closed tests.&lt;/li&gt;
&lt;li&gt;Define retention, incident revocation, rollback, and human approval.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Verified on &lt;strong&gt;2026-08-01&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="primary-sources"&gt;
&lt;a href="#primary-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Primary sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-oauth.html"&gt;AgentCore inbound and outbound authentication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/identity.html"&gt;AgentCore Identity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agentcore-vpc.html"&gt;AgentCore VPC and PrivateLink&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://csrc.nist.gov/publications/detail/sp/800-207/final"&gt;NIST Zero Trust Architecture&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Validate Policy Claims with Amazon Bedrock Automated Reasoning Checks</title><link>https://www.yopa.page/blog/2025-12-16-building-responsible-ai-with-amazon-bedrock.html</link><pubDate>Tue, 16 Dec 2025 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2025-12-16-building-responsible-ai-with-amazon-bedrock.html</guid><description>
&lt;p&gt;Amazon Bedrock Automated Reasoning checks can validate whether translated claims logically follow from a formalized policy. The solver is deterministic for the formal problem it receives. The overall system is not: natural-language policy translation can be incomplete or ambiguous, input may be out of scope, and a valid claim can still be unsafe for reasons the policy does not encode.&lt;/p&gt;
&lt;p&gt;Use Automated Reasoning as one policy-verification layer, not as universal authorization or a guarantee that an answer is true.&lt;/p&gt;
&lt;h2 id="build-and-version-the-policy"&gt;
&lt;a href="#build-and-version-the-policy" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Build and version the policy
&lt;/h2&gt;
&lt;p&gt;Create a focused policy from authoritative source documents. Review extracted variables, types, and rules with domain owners. Test contradictory, boundary, missing-information, and out-of-domain examples. Keep unrelated domains in separate policies.&lt;/p&gt;
&lt;p&gt;The working &lt;code&gt;DRAFT&lt;/code&gt; changes during development. Publish an immutable numbered policy and guardrail version for production so an edit cannot silently change deployed behavior. Record source-document version, reviewer, tests, and release date.&lt;/p&gt;
&lt;h2 id="validate-with-applyguardrail"&gt;
&lt;a href="#validate-with-applyguardrail" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Validate with &lt;code&gt;ApplyGuardrail&lt;/code&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ApplyGuardrail&lt;/code&gt; is the recommended standalone API when the application controls generation and validation separately. Provide at least one claim block. Include the user query as context when the policy decision depends on stated conditions.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;boto3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bedrock-runtime&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;us-east-1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;apply_guardrail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;guardrailIdentifier&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;guardrail-id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;guardrailVersion&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;3&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;OUTPUT&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;User: I bought it 45 days ago.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;qualifiers&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;query&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]}},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Assistant: You qualify for a refund.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;qualifiers&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;guard_content&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]}},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Do not only check &lt;code&gt;response[&amp;quot;action&amp;quot;]&lt;/code&gt;. Inspect &lt;code&gt;assessments[].automatedReasoningPolicy.findings&lt;/code&gt;. A finding can be valid, invalid, satisfiable, impossible, translation-ambiguous, too complex, or have no translations. Only &lt;code&gt;invalid&lt;/code&gt; directly establishes contradiction under the translated policy. Ambiguous or missing translations should trigger clarification, safe fallback, or human review—not approval.&lt;/p&gt;
&lt;p&gt;Use a numbered version in production, not &lt;code&gt;DRAFT&lt;/code&gt;. Confirm Automated Reasoning actually ran by monitoring assessment and usage data; a mis-tagged &lt;code&gt;Converse&lt;/code&gt; request can succeed without performing the intended check.&lt;/p&gt;
&lt;h2 id="enforcement-flow"&gt;
&lt;a href="#enforcement-flow" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Enforcement flow
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Authenticate and authorize the user&amp;rsquo;s requested action in application code.&lt;/li&gt;
&lt;li&gt;Retrieve the applicable versioned policy and required factual inputs from trusted systems.&lt;/li&gt;
&lt;li&gt;Generate or receive a proposed claim/action.&lt;/li&gt;
&lt;li&gt;Run Automated Reasoning and interpret all findings.&lt;/li&gt;
&lt;li&gt;Block invalid results; clarify satisfiable/ambiguous cases; escalate complex/no-translation cases.&lt;/li&gt;
&lt;li&gt;Re-check mutable business data immediately before execution.&lt;/li&gt;
&lt;li&gt;Record policy version, non-sensitive inputs, findings, decision, actor, and request ID.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Guardrails do not grant refunds, approve loans, or authorize records. The transactional service must enforce identity, permissions, limits, state, idempotency, and audit.&lt;/p&gt;
&lt;h2 id="other-guardrail-layers"&gt;
&lt;a href="#other-guardrail-layers" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Other guardrail layers
&lt;/h2&gt;
&lt;p&gt;Denied topics, content and word filters, sensitive-information filters, and contextual grounding checks solve different problems. Apply the smallest relevant set and understand qualifiers. Contextual grounding evaluates support/relevance against supplied source and query; it is not the same as formal policy entailment.&lt;/p&gt;
&lt;h2 id="evaluation-and-operations"&gt;
&lt;a href="#evaluation-and-operations" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Evaluation and operations
&lt;/h2&gt;
&lt;p&gt;Maintain golden policy cases, adversarial paraphrases, omitted facts, conflicting premises, out-of-domain requests, multilingual inputs, and policy-version regression tests. Measure false allow, false block, ambiguous/no-translation rate, latency, cost, and human escalation. Sample production decisions under privacy controls and provide a kill switch or conservative fallback.&lt;/p&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Replace deterministic-safety claims with a bounded policy-check description.&lt;/li&gt;
&lt;li&gt;Review the translated formal policy with domain experts.&lt;/li&gt;
&lt;li&gt;Publish and pin numbered versions.&lt;/li&gt;
&lt;li&gt;Inspect findings, not only intervention action.&lt;/li&gt;
&lt;li&gt;Add explicit handling for every finding type.&lt;/li&gt;
&lt;li&gt;Keep authorization and transaction controls outside the model/guardrail.&lt;/li&gt;
&lt;li&gt;Add regression evaluation, monitoring, audit, and rollback to the prior policy version.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Verified on &lt;strong&gt;2026-08-01&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="primary-sources"&gt;
&lt;a href="#primary-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Primary sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/integrate-automated-reasoning-checks.html"&gt;Integrate Automated Reasoning checks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/automated-reasoning-checks-concepts.html"&gt;Automated Reasoning concepts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails-contextual-grounding-check.html"&gt;Contextual grounding checks&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Amazon Bedrock AgentCore: A Service Map for Production Boundaries</title><link>https://www.yopa.page/blog/2026-08-01-agentcore-service-map-and-production-boundaries.html</link><pubDate>Mon, 15 Dec 2025 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-agentcore-service-map-and-production-boundaries.html</guid><description>
&lt;p&gt;Amazon Bedrock AgentCore is a collection of managed capabilities, not an “infrastructure-as-code layer” that automatically makes an agent production-ready. Each service solves a different boundary, exposes separate control- and data-plane APIs, and still requires application authorization, evaluation, cost controls, and recovery design.&lt;/p&gt;
&lt;h2 id="runtime"&gt;
&lt;a href="#runtime" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Runtime
&lt;/h2&gt;
&lt;p&gt;AgentCore Runtime hosts an agent application in an isolated runtime session. It supports HTTP, MCP, and A2A protocol contracts with different required ports and paths. An invocation uses either IAM SigV4 or configured JWT authorization. Runtime lifecycle settings control idle timeout and instance maximum lifetime; durable business state must live outside the runtime.&lt;/p&gt;
&lt;p&gt;Package a stateless application, pin dependencies and image digest, give its execution role only required AWS actions, and test the exact protocol contract. Do not assume a base image or SDK helper from a conference example remains current.&lt;/p&gt;
&lt;h2 id="identity"&gt;
&lt;a href="#identity" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Identity
&lt;/h2&gt;
&lt;p&gt;AgentCore Identity manages workload identities and credential providers for outbound access. It can broker OAuth 2.0 or API-key credentials without embedding them in prompts or source code. Inbound runtime authentication and outbound tool authorization are different decisions. A valid user JWT does not automatically authorize a tool action, and user context propagation must be designed explicitly.&lt;/p&gt;
&lt;h2 id="gateway"&gt;
&lt;a href="#gateway" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Gateway
&lt;/h2&gt;
&lt;p&gt;Gateway exposes Lambda functions, APIs, and other targets as MCP-compatible tools. Define narrow schemas, validate at the target, and authorize against the authenticated actor. Request/response interceptors can run Lambda code for transformation or custom policy, but they have limits, retries, latency, and sensitive-header handling requirements. Gateway is not a substitute for the target system&amp;rsquo;s own authorization.&lt;/p&gt;
&lt;h2 id="memory"&gt;
&lt;a href="#memory" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Memory
&lt;/h2&gt;
&lt;p&gt;Memory stores short-term events under actor/session identifiers and can asynchronously extract long-term records with configured strategies and namespace templates. Applications call event and retrieval APIs; they decide what data is appropriate to store and inject. Namespace organization helps isolation but does not replace IAM and tenant authorization. Set retention, KMS, deletion, consent, and evaluation for extracted memories.&lt;/p&gt;
&lt;h2 id="browser-and-code-interpreter"&gt;
&lt;a href="#browser-and-code-interpreter" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Browser and Code Interpreter
&lt;/h2&gt;
&lt;p&gt;These tools provide managed execution environments for browsing and code. They reduce infrastructure work, not risk. Browser content is untrusted and can contain prompt injection. Code execution needs input/output limits, network policy, artifact scanning, time budgets, and a decision about which generated files can leave the sandbox.&lt;/p&gt;
&lt;h2 id="observability"&gt;
&lt;a href="#observability" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Observability
&lt;/h2&gt;
&lt;p&gt;Use traces, logs, and metrics to connect model calls, tool decisions, gateway targets, memory operations, and user-visible outcomes. Redact prompts, tokens, credentials, and sensitive tool payloads. Track task success, unsafe-action rate, human escalation, latency, token/tool cost, retries, and recovery—not only model token counts.&lt;/p&gt;
&lt;h2 id="a-production-selection-process"&gt;
&lt;a href="#a-production-selection-process" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A production selection process
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Start with the application and threat model; select only services that solve a stated requirement.&lt;/li&gt;
&lt;li&gt;Draw identities and authorization at every hop.&lt;/li&gt;
&lt;li&gt;Define durable state, idempotency, timeout, retry, compensation, and human gates.&lt;/li&gt;
&lt;li&gt;Pin protocol, SDK, image, and model versions where supported.&lt;/li&gt;
&lt;li&gt;Build negative tests for denied tools, cross-user memory, malicious content, unavailable dependencies, and budget exhaustion.&lt;/li&gt;
&lt;li&gt;Deploy a read-only or draft-producing pilot, compare against a simpler baseline, then expand authority gradually.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;AgentCore can reduce undifferentiated hosting and integration work, but it introduces AWS-specific services, quotas, Regions, IAM policy, and variable cost. A self-hosted runtime or ordinary service may be simpler for a narrow agent. A workflow engine is usually better for deterministic, durable business orchestration.&lt;/p&gt;
&lt;p&gt;Before adoption, estimate steady and burst traffic, session lifetime, tool calls, memory extraction, observability volume, and idle resources. Confirm quotas and pricing in the chosen Region, then set budget alarms and load-test the failure limits.&lt;/p&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Remove illustrative APIs that are not present in current SDK documentation.&lt;/li&gt;
&lt;li&gt;Separate control-plane provisioning from data-plane invocation.&lt;/li&gt;
&lt;li&gt;Replace broad roles with per-component least privilege.&lt;/li&gt;
&lt;li&gt;Move secrets to Identity/Secrets Manager and durable state to explicit stores.&lt;/li&gt;
&lt;li&gt;Add retention/deletion policy for memory and logs.&lt;/li&gt;
&lt;li&gt;Establish service quotas, cost alarms, evaluation gates, and rollback.&lt;/li&gt;
&lt;li&gt;Verify each capability in the intended Region and account before claiming production support.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Verified on &lt;strong&gt;2026-08-01&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="primary-sources"&gt;
&lt;a href="#primary-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Primary sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/what-is-bedrock-agentcore.html"&gt;Amazon Bedrock AgentCore documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/identity.html"&gt;AgentCore Identity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/gateway-create.html"&gt;Create an AgentCore Gateway&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/memory.html"&gt;AgentCore Memory&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Real-Time Voice Agents with Amazon Nova 2 Sonic</title><link>https://www.yopa.page/blog/2026-08-01-real-time-voice-agents-with-nova-2-sonic.html</link><pubDate>Sun, 14 Dec 2025 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-real-time-voice-agents-with-nova-2-sonic.html</guid><description>
&lt;p&gt;A good voice agent is not simply a text agent with speech recognition and synthesis attached. Users speak over it, change their minds while a tool runs, pause unpredictably, and expect private audio to disappear when the conversation ends. Amazon Nova 2 Sonic handles speech-to-speech interaction through Amazon Bedrock’s bidirectional streaming API, but the application still owns transport, playback, tools, identity, and consent.&lt;/p&gt;
&lt;h2 id="put-a-trusted-relay-between-browser-and-bedrock"&gt;
&lt;a href="#put-a-trusted-relay-between-browser-and-bedrock" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Put a trusted relay between browser and Bedrock
&lt;/h2&gt;
&lt;p&gt;Do not place AWS credentials in a browser or mobile client. Capture microphone frames on the client and send them over a protected WebSocket or WebRTC connection to an authenticated backend. The backend uses SigV4 credentials to open &lt;code&gt;InvokeModelWithBidirectionalStream&lt;/code&gt; and relays structured events.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-mermaid" data-lang="mermaid"&gt;flowchart LR
Mic[Microphone] --&amp;gt; Client[Client audio queue]
Client --&amp;gt;|authenticated stream| Relay[Application relay]
Relay --&amp;gt;|SigV4 bidirectional API| Nova[Nova 2 Sonic]
Nova --&amp;gt; Relay
Relay --&amp;gt;|audio and events| Client
Relay --&amp;gt; Tools[Authorized tools]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Use the model ID documented for the deployment Region, currently &lt;code&gt;amazon.nova-2-sonic-v1:0&lt;/code&gt; in the AWS getting-started example. Model availability changes, so keep it in validated configuration rather than hard-coding it across clients.&lt;/p&gt;
&lt;p&gt;The API is event-driven. Start a session, start a prompt, then frame each piece of system, user, assistant, tool, or system-speech content with &lt;code&gt;contentStart&lt;/code&gt;, its content event, and &lt;code&gt;contentEnd&lt;/code&gt;. Maintain prompt, content, completion, session, and tool-use identifiers exactly as the protocol returns them. Use the official full-featured sample as the starting point; the basic sample intentionally omits true bidirectional behavior and barge-in.&lt;/p&gt;
&lt;h2 id="make-interruption-a-client-state-transition"&gt;
&lt;a href="#make-interruption-a-client-state-transition" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Make interruption a client state transition
&lt;/h2&gt;
&lt;p&gt;When the user interrupts, Nova 2 Sonic stops generating speech and emits an interruption notification. The client must immediately stop the audio device and discard queued assistant audio associated with the interrupted completion. Merely stopping the network stream leaves buffered speech playing, which makes the agent appear to ignore the user.&lt;/p&gt;
&lt;p&gt;Keep playback data keyed by completion ID. On interruption, invalidate that ID; reject late chunks from it; keep capturing microphone audio; and update the UI from “speaking” to “listening.” Test with headphones and speakers, because echo cancellation, device latency, and browser scheduling change behavior.&lt;/p&gt;
&lt;h2 id="asynchronous-tools-need-application-control"&gt;
&lt;a href="#asynchronous-tools-need-application-control" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Asynchronous tools need application control
&lt;/h2&gt;
&lt;p&gt;Nova 2 Sonic can continue listening and responding while tools execute. A changed request does not automatically cancel an earlier tool, and the result is still delivered to the model. Therefore tool execution needs explicit lifecycle state.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;execute_tool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;validate_schema&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;principal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;idempotency_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tool_use_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;timeout_seconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;redact_and_bound&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The model’s tool selection is not authorization. Validate schemas, enforce tenant and resource ownership outside the model, set timeouts and size limits, and require confirmation for consequential actions. If an obsolete tool result returns after the user changes intent, mark its generation or request version so the application can prevent stale writes even though the result is delivered to the conversation.&lt;/p&gt;
&lt;p&gt;For RAG, wrap retrieval as a read-only tool and return a small set of cited passages. Do not give retrieved text authority over system instructions. For complex workflows, call a durable service rather than hiding a long-running agent behind a voice tool timeout.&lt;/p&gt;
&lt;h2 id="privacy-and-safety-are-product-behavior"&gt;
&lt;a href="#privacy-and-safety-are-product-behavior" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Privacy and safety are product behavior
&lt;/h2&gt;
&lt;p&gt;Ask for microphone permission in context and visibly show recording state. Offer mute and immediate end controls. Decide whether audio, transcripts, tool inputs, and synthesized responses are stored; default to the least retention compatible with the product. Redact sensitive values before general logging, encrypt retained artifacts, and document deletion.&lt;/p&gt;
&lt;p&gt;Tell users they are speaking to AI. For regulated or high-impact actions, repeat the interpreted request and obtain confirmation. Provide a human handoff when confidence is low or the user asks for one. Evaluate accents, speech impairments, background noise, languages, and unsafe-content behavior with representative participants.&lt;/p&gt;
&lt;h2 id="connection-renewal-and-measurement"&gt;
&lt;a href="#connection-renewal-and-measurement" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Connection renewal and measurement
&lt;/h2&gt;
&lt;p&gt;AWS documents an eight-minute connection limit and provides a renewal/continuation pattern. Renew before the deadline, preserve only the approved conversation context, and make reconnect visible when continuity cannot be guaranteed.&lt;/p&gt;
&lt;p&gt;Measure microphone-to-first-audio latency, interruption-to-silence latency, turn-end detection errors, reconnect success, tool latency, task success, and user corrections. Log event timings and identifiers without storing raw audio by default. Load-test concurrent streams and enforce per-user quotas so one client cannot exhaust streaming capacity.&lt;/p&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;p&gt;Move from Nova Sonic v1 examples to the Nova 2 documentation and model configuration. Replace ad hoc WebSocket-to-model assumptions with the supported bidirectional event sequence. Adopt the full-featured sample, implement completion-aware buffer clearing, add asynchronous tool state and authorization, then run privacy and interruption acceptance tests on real devices before rollout.&lt;/p&gt;
&lt;h2 id="references"&gt;
&lt;a href="#references" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
References
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/nova/latest/nova2-userguide/sonic-getting-started.html"&gt;Getting started with Nova 2 Sonic speech-to-speech&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/nova/latest/nova2-userguide/sonic-input-events.html"&gt;Nova 2 Sonic input events&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/nova/latest/nova2-userguide/sonic-output-events.html"&gt;Nova 2 Sonic output events and barge-in&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/nova/latest/nova2-userguide/sonic-async-tools.html"&gt;Nova 2 Sonic asynchronous tool calling&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/nova/latest/nova2-userguide/sonic-code-examples.html"&gt;Nova 2 Sonic code examples&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Reviewed against official documentation on 2026-08-01.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Multi-Tenant Isolation with Amazon Bedrock AgentCore</title><link>https://www.yopa.page/blog/2026-08-01-multi-tenant-isolation-with-agentcore.html</link><pubDate>Sat, 13 Dec 2025 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-multi-tenant-isolation-with-agentcore.html</guid><description>
&lt;p&gt;Multi-tenant agents add a dangerous ambiguity: the model can talk about a tenant, but only trusted application code can establish which tenant the caller belongs to. Amazon Bedrock AgentCore provides useful isolation primitives, yet none replaces an end-to-end tenant authorization design.&lt;/p&gt;
&lt;p&gt;Start by separating four identities: the authenticated human or workload, the tenant, the AgentCore runtime session, and the AWS execution role. They may be related, but they are not interchangeable. In particular, a runtime session ID isolates runtime state; it is not proof of tenant membership.&lt;/p&gt;
&lt;h2 id="establish-tenant-context-before-the-model"&gt;
&lt;a href="#establish-tenant-context-before-the-model" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Establish tenant context before the model
&lt;/h2&gt;
&lt;p&gt;Authenticate at the edge. Resolve tenant membership from a trusted directory or entitlement service. Construct server-side request context and reject any tenant identifier supplied only by the prompt or browser.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;principalId&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;user-7c91&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;tenantId&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tenant-42&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;roles&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;case-reader&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;requestId&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;req-a813&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Sign or integrity-protect this context between services and validate audience, issuer, expiry, and tenant membership at every trust boundary. Do not ask the model whether a request is allowed. The model can help interpret intent; a deterministic policy layer must authorize the resulting operation.&lt;/p&gt;
&lt;h2 id="choose-the-isolation-tier-deliberately"&gt;
&lt;a href="#choose-the-isolation-tier-deliberately" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Choose the isolation tier deliberately
&lt;/h2&gt;
&lt;p&gt;A pooled runtime is efficient, while dedicated resources reduce blast radius. Use pooled compute with strong logical controls for lower-risk workloads. Consider separate runtime, memory, KMS key, network path, or AWS account for regulated data or tenants requiring hard isolation. Document which components are pooled and which are siloed.&lt;/p&gt;
&lt;p&gt;AgentCore Runtime creates isolated session execution environments. Still, never cache tenant data in process-global variables, reuse temporary files across sessions, or derive a tenant from previous conversation content. Keep each invocation and tool request bound to validated server context.&lt;/p&gt;
&lt;h2 id="make-memory-isolation-enforceable"&gt;
&lt;a href="#make-memory-isolation-enforceable" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Make memory isolation enforceable
&lt;/h2&gt;
&lt;p&gt;AgentCore Memory events are organized by &lt;code&gt;actorId&lt;/code&gt; and &lt;code&gt;sessionId&lt;/code&gt;; long-term records use configured namespaces. Use opaque actor IDs mapped server-side to tenant and user. A useful namespace includes the strategy and trusted tenant/actor scope, for example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/tenant/tenant-42/strategy/{memoryStrategyId}/actor/user-7c91/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Organization alone is not access control. Restrict the specific memory ARN and permitted namespace with documented IAM condition keys such as &lt;code&gt;bedrock-agentcore:namespace&lt;/code&gt; and &lt;code&gt;bedrock-agentcore:namespacePath&lt;/code&gt;. If one shared execution role can calculate any tenant path, IAM alone may not prevent confused-deputy bugs; add a policy decision point or assume tenant-scoped roles with session tags from a trusted broker.&lt;/p&gt;
&lt;p&gt;Avoid placing raw tenant IDs in logs or URLs when opaque identifiers work. Define deletion, export, and retention by tenant and actor before enabling long-term strategies.&lt;/p&gt;
&lt;h2 id="authorize-every-tool-and-data-query"&gt;
&lt;a href="#authorize-every-tool-and-data-query" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Authorize every tool and data query
&lt;/h2&gt;
&lt;p&gt;An MCP or A2A boundary does not make a tool tenant-aware. Pass trusted context out of band from model arguments. At the tool service, derive the permitted tenant and add it to every storage query. Prefer database row-level security or partition keys plus authorization over a prompt instruction such as “only access this tenant.”&lt;/p&gt;
&lt;p&gt;For writes, validate resource ownership, action, amount, and state. Use idempotency keys and require step-up approval for consequential actions. Downstream credentials should be short-lived and least-privileged. Do not forward the caller’s bearer token to an arbitrary agent selected from an Agent Card.&lt;/p&gt;
&lt;h2 id="observe-without-leaking"&gt;
&lt;a href="#observe-without-leaking" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Observe without leaking
&lt;/h2&gt;
&lt;p&gt;Include request, tenant, actor, session, tool, policy decision, and trace identifiers in structured audit records, but keep prompts and tool payloads out of general logs by default. Encrypt tenant artifacts, use tenant-aware metric dimensions only where cardinality and privacy allow, and alert on cross-tenant denials. Log policy inputs and outcomes so an investigation does not depend on model prose.&lt;/p&gt;
&lt;h2 id="migration-and-isolation-testing"&gt;
&lt;a href="#migration-and-isolation-testing" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration and isolation testing
&lt;/h2&gt;
&lt;p&gt;Before pooling an existing single-tenant agent, inventory every state location: runtime memory, AgentCore Memory, caches, object prefixes, vector indexes, database rows, queues, traces, and analytics exports. Add tenant keys and authorization first, backfill data with reconciliation, then enable pooled traffic gradually.&lt;/p&gt;
&lt;p&gt;Build a two-tenant adversarial suite. Reuse session IDs, swap actor IDs, forge context, request another tenant’s object through a tool, inject identifiers through retrieved documents, retry old requests, and inspect logs and caches. Test both allowed and denied cases. Run canaries that place unique markers in each tenant and continuously verify that search, memory retrieval, tools, exports, and deletion never cross the boundary.&lt;/p&gt;
&lt;h2 id="references"&gt;
&lt;a href="#references" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
References
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agents-tools-runtime.html"&gt;AgentCore Runtime overview and session isolation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/specify-long-term-memory-organization.html"&gt;Organize AgentCore long-term memory with namespaces&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_session-tags.html"&gt;IAM session tags&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/whitepapers/latest/saas-tenant-isolation-strategies/saas-tenant-isolation-strategies.html"&gt;SaaS Tenant Isolation Strategies whitepaper&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Reviewed against official documentation on 2026-08-01.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Designing Multi-Agent Systems Only When One Agent Is Not Enough</title><link>https://www.yopa.page/blog/2025-12-12-designing-robust-multi-agent-systems.html</link><pubDate>Fri, 12 Dec 2025 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2025-12-12-designing-robust-multi-agent-systems.html</guid><description>
&lt;p&gt;Multiple agents add coordination; they do not automatically add intelligence. Begin with one agent and a small typed tool set. Add another agent only when an explicit boundary—context, skill evaluation, security, ownership, or parallelism—improves a measured outcome.&lt;/p&gt;
&lt;h2 id="four-useful-structures"&gt;
&lt;a href="#four-useful-structures" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Four useful structures
&lt;/h2&gt;
&lt;h3 id="single-agent-with-tools"&gt;
&lt;a href="#single-agent-with-tools" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Single agent with tools
&lt;/h3&gt;
&lt;p&gt;This is the baseline. One policy, one context owner, and one trace make failures easier to understand. It often beats a multi-agent design for short tasks and shared context.&lt;/p&gt;
&lt;h3 id="supervisor-and-specialists"&gt;
&lt;a href="#supervisor-and-specialists" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Supervisor and specialists
&lt;/h3&gt;
&lt;p&gt;A supervisor decomposes work and delegates to specialists with narrow contracts. It is useful when specialists need distinct context or tools. The supervisor should receive structured results and evidence, not every raw page or tool trace. “Context hiding” is not deleting relevant facts; it is deliberate summarization with source references and uncertainty.&lt;/p&gt;
&lt;p&gt;Failure modes include a weak decomposition, repeated delegation, lossy summaries, and a supervisor accepting an invalid result. Set delegation depth, time/token/tool budgets, schemas, and a fallback to human review.&lt;/p&gt;
&lt;h3 id="peer-to-peer-a2a"&gt;
&lt;a href="#peer-to-peer-a2a" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Peer-to-peer A2A
&lt;/h3&gt;
&lt;p&gt;Peer agents expose capabilities and communicate through a protocol such as A2A. This fits independently owned services, but discovery, authentication, authorization, version compatibility, deadlines, and audit are platform responsibilities. Do not let a caller&amp;rsquo;s text claim establish user identity.&lt;/p&gt;
&lt;h3 id="deterministic-workflow-or-dag"&gt;
&lt;a href="#deterministic-workflow-or-dag" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Deterministic workflow or DAG
&lt;/h3&gt;
&lt;p&gt;When dependencies and transitions are known, a workflow engine should own them. It provides durable state, retries, timeouts, compensation, and approval. Model steps can classify, draft, or recommend within nodes. This is usually more reliable than asking an LLM to remember the workflow.&lt;/p&gt;
&lt;p&gt;ReWOO and plan-then-execute techniques can reduce repeated observation and model calls, but a precomputed plan can become stale. Add checkpoints that revalidate assumptions before consequential actions.&lt;/p&gt;
&lt;h2 id="contract-for-every-delegation"&gt;
&lt;a href="#contract-for-every-delegation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Contract for every delegation
&lt;/h2&gt;
&lt;p&gt;Define objective, allowed inputs, tool authority, output schema, evidence, deadline, idempotency key, and failure behavior. A specialist should return &lt;code&gt;status&lt;/code&gt;, result, sources, uncertainty, and safe retry information. Treat its output as untrusted at the next boundary.&lt;/p&gt;
&lt;p&gt;Propagate authenticated identity separately from model content. Re-authorize tool calls at the target. Give specialists only their needed tools, and keep write operations behind human or policy gates.&lt;/p&gt;
&lt;h2 id="evaluate-the-system-not-the-conversation"&gt;
&lt;a href="#evaluate-the-system-not-the-conversation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Evaluate the system, not the conversation
&lt;/h2&gt;
&lt;p&gt;Create a single-agent baseline and compare:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;end-to-end task success and factual grounding;&lt;/li&gt;
&lt;li&gt;incorrect delegation and invalid handoff rate;&lt;/li&gt;
&lt;li&gt;unauthorized or unnecessary tool attempts;&lt;/li&gt;
&lt;li&gt;latency, token/tool cost, and retry amplification;&lt;/li&gt;
&lt;li&gt;recovery from timeout, duplicate message, partial result, and unavailable specialist;&lt;/li&gt;
&lt;li&gt;reviewer correction and escalation quality.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Run adversarial tests with prompt injection in retrieved content and agent messages. Test cyclic delegation, conflicting specialists, stale memory, budget exhaustion, and one agent returning a syntactically valid but false result.&lt;/p&gt;
&lt;h2 id="operational-pattern"&gt;
&lt;a href="#operational-pattern" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Operational pattern
&lt;/h2&gt;
&lt;p&gt;Store durable task state outside prompts. Use correlation IDs and traces across hops, redact sensitive content, and record model/tool/config versions. Cap concurrency to protect downstream services. Make actions idempotent. Provide cancellation and a kill switch. A session that times out must be resumable from durable state or fail explicitly.&lt;/p&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Measure the current single-agent baseline.&lt;/li&gt;
&lt;li&gt;Identify the precise boundary each proposed agent owns.&lt;/li&gt;
&lt;li&gt;Replace free-form handoffs with versioned schemas and evidence.&lt;/li&gt;
&lt;li&gt;Put known sequencing in a workflow engine.&lt;/li&gt;
&lt;li&gt;Add hop-by-hop identity and authorization.&lt;/li&gt;
&lt;li&gt;Set delegation, time, cost, and tool budgets.&lt;/li&gt;
&lt;li&gt;Add fault injection and security evaluation.&lt;/li&gt;
&lt;li&gt;Remove agents that do not improve success enough to justify complexity.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Verified against the &lt;a href="https://a2a-protocol.org/latest/specification/"&gt;A2A specification&lt;/a&gt;, &lt;a href="https://modelcontextprotocol.io/specification/"&gt;MCP specification&lt;/a&gt;, and &lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/welcome.html"&gt;AWS Step Functions guidance&lt;/a&gt; on &lt;strong&gt;2026-08-01&lt;/strong&gt;.&lt;/p&gt;</description></item><item><title>MCP and A2A Boundaries on Amazon Bedrock AgentCore</title><link>https://www.yopa.page/blog/2026-08-01-mcp-and-a2a-boundaries-on-agentcore.html</link><pubDate>Thu, 11 Dec 2025 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-mcp-and-a2a-boundaries-on-agentcore.html</guid><description>
&lt;p&gt;Model Context Protocol (MCP) and Agent-to-Agent (A2A) are not “hands” and “voices” that every agent must have. They are different wire contracts. MCP exposes tools and contextual resources to a model-facing client. A2A exposes an agent, its capabilities, and task-oriented interaction to another application or agent. Sometimes plain HTTP is the smaller and better boundary.&lt;/p&gt;
&lt;p&gt;Amazon Bedrock AgentCore Runtime supports all three. The choice affects the port, mount path, message format, discovery mechanism, and client expectations.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Contract&lt;/th&gt;
&lt;th&gt;Runtime endpoint&lt;/th&gt;
&lt;th&gt;Discovery&lt;/th&gt;
&lt;th&gt;Good fit&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HTTP&lt;/td&gt;
&lt;td&gt;port 8080, &lt;code&gt;/invocations&lt;/code&gt; or &lt;code&gt;/ws&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;application-defined&lt;/td&gt;
&lt;td&gt;direct application calls and streaming&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MCP&lt;/td&gt;
&lt;td&gt;port 8000, &lt;code&gt;/mcp&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;tool listing&lt;/td&gt;
&lt;td&gt;bounded tools or an MCP server&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;A2A&lt;/td&gt;
&lt;td&gt;port 9000, &lt;code&gt;/&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Agent Card&lt;/td&gt;
&lt;td&gt;interoperable agent delegation&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Do not run several contracts behind one runtime and infer the requested protocol from payload shape. Deploy and test each public contract independently.&lt;/p&gt;
&lt;h2 id="use-mcp-for-constrained-capabilities"&gt;
&lt;a href="#use-mcp-for-constrained-capabilities" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Use MCP for constrained capabilities
&lt;/h2&gt;
&lt;p&gt;An MCP tool should have a narrow name, JSON schema, predictable errors, and an authorization check at execution time. The model choosing a tool is not approval. Validate arguments, bind the caller to permitted resources, rate-limit costly operations, and require confirmation for consequential writes.&lt;/p&gt;
&lt;p&gt;MCP is useful when multiple compatible clients need the same tools. A normal internal function is preferable when there is only one process and no interoperability requirement. Adding a network protocol creates authentication, versioning, observability, and availability work.&lt;/p&gt;
&lt;p&gt;Treat tool descriptions and returned text as untrusted content. A document fetched through MCP can contain instructions aimed at the model. Keep system policy separate, label provenance, sanitize output used in another command, and never turn retrieved text directly into shell or database operations.&lt;/p&gt;
&lt;h2 id="use-a2a-for-an-agent-shaped-service"&gt;
&lt;a href="#use-a2a-for-an-agent-shaped-service" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Use A2A for an agent-shaped service
&lt;/h2&gt;
&lt;p&gt;AgentCore’s A2A contract uses JSON-RPC 2.0 over HTTP and serves the Agent Card at &lt;code&gt;/.well-known/agent-card.json&lt;/code&gt;. The Agent Card is metadata for discovery; it is not a search engine, trust proof, or authorization grant. Production routing should use an allow-listed registry and verify the destination’s identity and advertised capabilities.&lt;/p&gt;
&lt;p&gt;The current AgentCore and Strands integration wraps a Strands agent with the supported executor and runtime helper:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;strands.multiagent.a2a.executor&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StrandsA2AExecutor&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bedrock_agentcore.runtime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;serve_a2a&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nd"&gt;@tool&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;summarize_case&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;case_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;Return an authorized case summary for the current caller.&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;load_authorized_summary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;case_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;summarize_case&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;__main__&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;serve_a2a&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;StrandsA2AExecutor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This establishes protocol compatibility; it does not implement business authorization. AgentCore can authenticate invocation with SigV4 or OAuth 2.0. The service must still decide whether that principal may read the requested case and invoke downstream tools. Do not forward a user token indiscriminately. Prefer token exchange or service credentials with explicit, least-privilege delegation, and record the initiating principal separately from the workload identity.&lt;/p&gt;
&lt;h2 id="propagate-context-deliberately"&gt;
&lt;a href="#propagate-context-deliberately" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Propagate context deliberately
&lt;/h2&gt;
&lt;p&gt;AgentCore adds a runtime session header for session isolation. Session identity is not tenant identity. Carry correlation, tenant, and end-user context in a signed or server-established envelope, validate it at every hop, and avoid trusting values generated by the model.&lt;/p&gt;
&lt;p&gt;For A2A, define timeouts and cancellation, bound task fan-out, and preserve a trace ID across calls. A delegation graph can otherwise loop or multiply costs. Return structured errors and avoid embedding secrets or raw internal stack traces in JSON-RPC error data.&lt;/p&gt;
&lt;h2 id="migration-and-evaluation"&gt;
&lt;a href="#migration-and-evaluation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration and evaluation
&lt;/h2&gt;
&lt;p&gt;Start with an inventory of existing tools and agent endpoints. Keep deterministic, narrow operations as functions or MCP tools. Expose A2A only where independent ownership, capability discovery, or cross-framework delegation provides real value. Put legacy adapters behind the new boundary and migrate clients gradually instead of changing orchestration and protocol simultaneously.&lt;/p&gt;
&lt;p&gt;Contract-test Agent Card retrieval, JSON-RPC errors, authentication failures, tool schemas, cancellation, and streaming. Security tests should include forged tenant context, prompt injection in tool output, a compromised downstream agent, excessive delegation, and replayed requests. Compare task success, latency, token use, tool error rate, and authorization denials before and after migration.&lt;/p&gt;
&lt;h2 id="references"&gt;
&lt;a href="#references" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
References
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-service-contract.html"&gt;AgentCore Runtime service contract&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-a2a.html"&gt;Deploy A2A servers in AgentCore Runtime&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-a2a-protocol-contract.html"&gt;AgentCore A2A protocol contract&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/specification/"&gt;Model Context Protocol specification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Reviewed against official documentation on 2026-08-01.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>AgentCore Memory Events, Strategies, and Isolation</title><link>https://www.yopa.page/blog/2026-08-01-agentcore-memory-events-strategies-and-isolation.html</link><pubDate>Tue, 09 Dec 2025 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-agentcore-memory-events-strategies-and-isolation.html</guid><description>
&lt;p&gt;Agent memory is not one database that magically remembers a user. Amazon Bedrock AgentCore Memory has a concrete model: a memory resource stores immutable short-term events, while configured strategies asynchronously derive long-term memory records. Applications must choose actor and session identifiers, namespace layouts, retention, retrieval rules, and authorization.&lt;/p&gt;
&lt;h2 id="the-current-data-model"&gt;
&lt;a href="#the-current-data-model" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The current data model
&lt;/h2&gt;
&lt;p&gt;An event is a timestamped interaction associated with a &lt;code&gt;memoryId&lt;/code&gt;, &lt;code&gt;actorId&lt;/code&gt;, and &lt;code&gt;sessionId&lt;/code&gt;. A conversation turn, tool result, or application activity can become an event. Short-term retrieval follows the actor and session. If the memory resource has strategies, those strategies process events into records for long-term retrieval.&lt;/p&gt;
&lt;p&gt;Built-in strategies cover common needs such as semantic facts, summaries, user preferences, and episodic information. A self-managed strategy is appropriate when extraction must be deterministic, independently audited, or driven by domain logic. It lets an application extract records itself and use batch record APIs instead of inventing a nonexistent “save insight” hook.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-mermaid" data-lang="mermaid"&gt;flowchart LR
App[Application] --&amp;gt;|CreateEvent| Events[(Events)]
Events --&amp;gt; Strategy[Managed or custom strategy]
Strategy --&amp;gt;|asynchronous| Records[(Memory records)]
App --&amp;gt;|ListEvents| Events
App --&amp;gt;|RetrieveMemoryRecords| Records
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The write path and long-term read path have different consistency expectations. A newly created event can be read as short-term history, but derived records may not exist immediately. Design the prompt assembly path to tolerate that delay.&lt;/p&gt;
&lt;h2 id="write-events-through-the-supported-api"&gt;
&lt;a href="#write-events-through-the-supported-api" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Write events through the supported API
&lt;/h2&gt;
&lt;p&gt;The SDK helper below follows the documented session model and keeps actor and session explicit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bedrock_agentcore.memory&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MemorySessionManager&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;bedrock_agentcore.memory.constants&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ConversationalMessage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MessageRole&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;manager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MemorySessionManager&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;memory_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MEMORY_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;us-west-2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;create_memory_session&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;actor_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;stable_actor_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;session_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;conversation_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_turns&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ConversationalMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;I prefer email updates.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MessageRole&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;USER&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ConversationalMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;I&amp;#39;ll use email for updates.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;MessageRole&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ASSISTANT&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Generate &lt;code&gt;stable_actor_id&lt;/code&gt; in the trusted application tier after authentication. Do not accept an arbitrary actor ID from a browser and forward it unchanged. Session IDs should be unique, opaque, and bound to the authenticated actor in application state.&lt;/p&gt;
&lt;p&gt;Never place passwords, access tokens, payment data, or unnecessary personal data in events. Extraction can copy sensitive input into a durable record. Apply redaction before writing, define an event expiry duration, and implement deletion and subject-access workflows appropriate to the data.&lt;/p&gt;
&lt;h2 id="namespace-design-is-part-of-the-contract"&gt;
&lt;a href="#namespace-design-is-part-of-the-contract" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Namespace design is part of the contract
&lt;/h2&gt;
&lt;p&gt;Namespaces organize records produced by each strategy. Use documented template variables rather than string concatenation inside prompts. A typical actor-scoped shape is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/strategy/{memoryStrategyId}/actor/{actorId}/
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Include &lt;code&gt;{sessionId}&lt;/code&gt; only when memories should not carry across sessions. A global namespace makes sense only for intentionally shared, non-user-specific knowledge. Changing the namespace later creates a migration problem because old and new records live under different paths; version a namespace when its meaning changes.&lt;/p&gt;
&lt;p&gt;Namespace organization is not sufficient authorization. Enforce retrieval with IAM condition keys such as &lt;code&gt;bedrock-agentcore:namespace&lt;/code&gt; or &lt;code&gt;bedrock-agentcore:namespacePath&lt;/code&gt;, and constrain the memory resource ARN. The principal used by the application still needs a trusted mapping from identity to permitted namespace.&lt;/p&gt;
&lt;h2 id="retrieval-and-prompt-assembly"&gt;
&lt;a href="#retrieval-and-prompt-assembly" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Retrieval and prompt assembly
&lt;/h2&gt;
&lt;p&gt;Retrieve only the strategies and namespace needed for the current task. Apply a small result limit, filter by relevance, label memory as untrusted context, and avoid letting recalled text override system policy. When the system must know a fact—an account balance, entitlement, or approval—read the authoritative service instead of memory.&lt;/p&gt;
&lt;p&gt;Store provenance with application records: actor, session, event ID, strategy, namespace, creation time, and the prompt or extraction version where available. These fields make it possible to explain why a memory appeared and to remove bad records.&lt;/p&gt;
&lt;h2 id="migration-and-evaluation"&gt;
&lt;a href="#migration-and-evaluation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration and evaluation
&lt;/h2&gt;
&lt;p&gt;Replace custom hooks with &lt;code&gt;CreateEvent&lt;/code&gt; or the SDK session helper. Map old users to stable actors and old conversations to sessions. Choose strategies and namespaces before importing anything; use self-managed batch operations for vetted legacy facts. Run imports with checkpoints and keep a reconciliation report.&lt;/p&gt;
&lt;p&gt;Evaluate more than recall quality. Test cross-actor and cross-tenant denial, delayed extraction, deletion, poisoned instructions inside remembered text, contradictory updates, empty retrieval, and strategy changes. Measure whether relevant memories help task success without leaking information or displacing authoritative data.&lt;/p&gt;
&lt;h2 id="references"&gt;
&lt;a href="#references" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
References
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/memory-get-started.html"&gt;Get started with AgentCore Memory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/memory-terminology.html"&gt;Memory terminology&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/specify-long-term-memory-organization.html"&gt;Organize long-term memory with namespaces&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/memory-self-managed-strategies.html"&gt;Self-managed memory strategies&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Reviewed against official documentation on 2026-08-01.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Durable Long-Running Jobs with Amazon Bedrock AgentCore</title><link>https://www.yopa.page/blog/2026-08-01-durable-long-running-jobs-with-agentcore.html</link><pubDate>Mon, 08 Dec 2025 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-durable-long-running-jobs-with-agentcore.html</guid><description>
&lt;p&gt;Amazon Bedrock AgentCore Runtime can run asynchronous work for up to eight hours. That is useful, but it is not the same as durable execution. A fraud investigation that waits two days for a document, or an onboarding flow paused for manager approval, cannot depend on one runtime session staying alive.&lt;/p&gt;
&lt;p&gt;The useful boundary is simple: AgentCore Runtime performs bounded agent work; a durable orchestrator owns the business process.&lt;/p&gt;
&lt;h2 id="separate-runtime-state-from-workflow-state"&gt;
&lt;a href="#separate-runtime-state-from-workflow-state" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Separate runtime state from workflow state
&lt;/h2&gt;
&lt;p&gt;Treat a runtime invocation as replaceable. Give it an idempotent unit of work, persist results outside the runtime, and let AWS Step Functions or another durable workflow engine decide what happens next.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-mermaid" data-lang="mermaid"&gt;flowchart LR
API[Request] --&amp;gt; SFN[Step Functions]
SFN --&amp;gt; Q[SQS work item]
Q --&amp;gt; AC[AgentCore Runtime]
AC --&amp;gt; S3[(S3 artifact)]
AC --&amp;gt; DDB[(DynamoDB checkpoint)]
DDB --&amp;gt; SFN
SFN --&amp;gt;|approval token| Human[Reviewer]
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Use AgentCore Memory for conversational continuity, preferences, and extracted knowledge. Do not use it as the source of truth for job status, approvals, money movement, or exactly-once processing. Those belong in a transactional store with explicit state transitions.&lt;/p&gt;
&lt;h2 id="define-a-restart-safe-work-contract"&gt;
&lt;a href="#define-a-restart-safe-work-contract" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Define a restart-safe work contract
&lt;/h2&gt;
&lt;p&gt;Every work item should carry a stable job ID, step ID, attempt number, input reference, and output location. Avoid putting large or sensitive payloads directly on a queue.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;jobId&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;case-8f2c&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;stepId&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;summarize-evidence&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;attempt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;input&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;s3://private-case-data/case-8f2c/manifest.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;output&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;s3://private-case-data/case-8f2c/summary.json&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Before doing expensive work, conditionally claim the step in DynamoDB. Write the result to a versioned object, then conditionally mark the step complete. A retry should return the existing result instead of repeating an external action. For side-effecting tools, pass an idempotency key through to the downstream API when it supports one.&lt;/p&gt;
&lt;p&gt;The agent should emit structured progress rather than relying on an open HTTP response:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_step&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;artifacts&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;job_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;jobId&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;step_id&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;stepId&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;is_complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;lease&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;claim&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;attempt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;run_bounded_agent_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;input&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;uri&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;artifacts&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;put_json&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;output&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;complete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lease&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lease&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;result_uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;resultUri&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;uri&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This is application-level pseudocode: the important part is the conditional claim and completion, not a particular library.&lt;/p&gt;
&lt;h2 id="heartbeats-are-liveness-not-durability"&gt;
&lt;a href="#heartbeats-are-liveness-not-durability" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Heartbeats are liveness, not durability
&lt;/h2&gt;
&lt;p&gt;AgentCore Runtime supports background tasks and reports status through its runtime contract. Keep the runtime responsive to health checks while background work runs, and stay within the documented execution limit. A heartbeat only says the current process is alive; it does not recreate lost work after termination.&lt;/p&gt;
&lt;p&gt;Split work that might approach the limit. For example, process a corpus in batches and checkpoint after each batch. Let the orchestrator start the next invocation. If a task cannot be divided, run it on a compute service whose lifetime matches the task, while using AgentCore only for the reasoning steps.&lt;/p&gt;
&lt;h2 id="human-approval-and-external-callbacks"&gt;
&lt;a href="#human-approval-and-external-callbacks" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Human approval and external callbacks
&lt;/h2&gt;
&lt;p&gt;Never keep an invocation open while waiting for a person. Store the proposed action, stop processing, and use a Step Functions callback task or an application event to resume. Bind approval to the job, proposed action digest, approver, and expiry time. Revalidate authorization when resuming because permissions and input data may have changed.&lt;/p&gt;
&lt;h2 id="security-and-operations"&gt;
&lt;a href="#security-and-operations" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Security and operations
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Give the runtime role access only to the queue, table rows, artifact prefix, models, and tools it needs.&lt;/li&gt;
&lt;li&gt;Encrypt artifacts, avoid secrets in prompts and logs, and define retention for intermediate data.&lt;/li&gt;
&lt;li&gt;Treat tool output and retrieved documents as untrusted input. Validate schemas and require policy checks outside the model for consequential actions.&lt;/li&gt;
&lt;li&gt;Record job ID, step ID, runtime session ID, model version, prompt/template version, tool calls, and result URI in traces.&lt;/li&gt;
&lt;li&gt;Put bounded retries on transient failures, then route exhausted work to a dead-letter queue. Do not retry policy denials or malformed input.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="migration-from-a-single-long-session"&gt;
&lt;a href="#migration-from-a-single-long-session" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration from a single long session
&lt;/h2&gt;
&lt;p&gt;First inventory everything held only in process memory. Move business state and artifacts to durable stores. Divide the flow into restartable steps and add idempotency. Put waiting states in the orchestrator. Finally, kill workers during a staging run and verify the workflow resumes without duplicate side effects.&lt;/p&gt;
&lt;p&gt;Evaluate the design with four tests: a retry after a model timeout, termination midway through a batch, approval after the original session has expired, and duplicate delivery of the same queue message. The job is durable only when all four produce one correct final outcome.&lt;/p&gt;
&lt;h2 id="references"&gt;
&lt;a href="#references" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
References
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-how-it-works.html"&gt;How AgentCore Runtime works&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-service-contract.html"&gt;AgentCore Runtime service contract&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/step-functions/latest/dg/connect-to-resource.html#connect-wait-token"&gt;AWS Step Functions callback tasks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html"&gt;Amazon SQS visibility timeout&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;em&gt;Reviewed against official documentation on 2026-08-01.&lt;/em&gt;&lt;/p&gt;</description></item><item><title>Run n8n Queue Mode on AWS ECS with Recoverable Operations</title><link>https://www.yopa.page/blog/2026-08-01-run-n8n-queue-mode-on-aws-ecs.html</link><pubDate>Wed, 20 Aug 2025 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-run-n8n-queue-mode-on-aws-ecs.html</guid><description>
&lt;p&gt;n8n queue mode separates HTTP/editor work from workflow execution. The scalable unit is not “two workers behind two load balancers.” It is a set of explicit n8n process roles sharing a database, Redis queue, encryption key, and versioned configuration.&lt;/p&gt;
&lt;p&gt;This architecture is a starting point, not a claim that ECS is always better than n8n&amp;rsquo;s supported Kubernetes patterns or n8n Cloud.&lt;/p&gt;
&lt;h2 id="components-and-boundaries"&gt;
&lt;a href="#components-and-boundaries" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Components and boundaries
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Main/editor service:&lt;/strong&gt; owns the editor UI, API, scheduling, and workflow activation. Keep it private to trusted users through SSO/VPN/identity-aware ingress where possible.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Webhook processors:&lt;/strong&gt; receive production webhook traffic and enqueue executions. Expose only required webhook paths.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Workers:&lt;/strong&gt; run queued executions. They do not need public ingress.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PostgreSQL:&lt;/strong&gt; system of record for workflows, credentials metadata, and execution records. Use supported PostgreSQL versions, Multi-AZ where required, backups, and restore tests.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Redis:&lt;/strong&gt; queue coordination. Configure the n8n-supported Redis mode and durability/failover appropriate to acceptable work loss.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Task runners:&lt;/strong&gt; isolate user-supplied Code node execution according to current n8n guidance.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Use one Application Load Balancer with host/path routing unless separate load balancers provide a documented security, ownership, or scaling boundary. CORS is not the reason to split ingress; configure &lt;code&gt;N8N_EDITOR_BASE_URL&lt;/code&gt;, &lt;code&gt;WEBHOOK_URL&lt;/code&gt;, proxy trust, and allowed origins consistently.&lt;/p&gt;
&lt;h2 id="version-and-secrets"&gt;
&lt;a href="#version-and-secrets" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Version and secrets
&lt;/h2&gt;
&lt;p&gt;Pin the same n8n version or immutable image digest across main, webhook, workers, and runners. Never mix versions during steady state or deploy &lt;code&gt;latest&lt;/code&gt; to production.&lt;/p&gt;
&lt;p&gt;All roles need the same &lt;code&gt;N8N_ENCRYPTION_KEY&lt;/code&gt;. Store it in AWS Secrets Manager and plan backup/restore separately; losing it can make stored credentials unreadable. Supply database and Redis authentication as ECS secrets, not plain environment values in a task definition or CI log. Use distinct task and execution roles with least privilege.&lt;/p&gt;
&lt;p&gt;Representative environment names must be checked against the n8n version you pin:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;EXECUTIONS_MODE=queue
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DB_TYPE=postgresdb
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;DB_POSTGRESDB_HOST=&amp;lt;private endpoint&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;QUEUE_BULL_REDIS_HOST=&amp;lt;private endpoint&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;N8N_EDITOR_BASE_URL=https://n8n.example.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;WEBHOOK_URL=https://hooks.example.com
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Do not copy an old Compose variable list without validating current n8n documentation. Configuration names and task-runner requirements evolve.&lt;/p&gt;
&lt;h2 id="networking"&gt;
&lt;a href="#networking" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Networking
&lt;/h2&gt;
&lt;p&gt;Run tasks, Postgres, and Redis in private subnets. ALB listeners terminate TLS with ACM. Restrict security groups by connection path: ALB to HTTP roles, ECS roles to database/Redis, and no direct public database access. Provide controlled outbound access for workflow integrations through NAT or approved egress/proxy paths, understanding that arbitrary workflows can exfiltrate data if their network and credential authority is broad.&lt;/p&gt;
&lt;p&gt;Protect the editor more strongly than public webhook endpoints. Apply request limits and WAF rules only where they address measured threats. Verify n8n&amp;rsquo;s proxy and secure-cookie settings behind the ALB.&lt;/p&gt;
&lt;h2 id="scaling-and-reliability"&gt;
&lt;a href="#scaling-and-reliability" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Scaling and reliability
&lt;/h2&gt;
&lt;p&gt;Keep at least two tasks for roles that require availability across Availability Zones. Scale workers from queue depth and oldest-job age, capped by downstream API and database capacity; CPU alone does not describe backlog. Configure graceful shutdown so workers stop accepting jobs and finish or safely return work before ECS termination.&lt;/p&gt;
&lt;p&gt;Health checks should test the correct role endpoint, but an ALB health check does not prove a worker can decrypt credentials, query Postgres, reach Redis, or run a workflow. Add synthetic workflows and alarms for queue age, failed executions, worker availability, database connections/storage, Redis memory/failover, ALB 5xx, and ECS deployment failure.&lt;/p&gt;
&lt;h2 id="upgrade-and-rollback"&gt;
&lt;a href="#upgrade-and-rollback" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Upgrade and rollback
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Read n8n release and migration notes; test every intermediate version required.&lt;/li&gt;
&lt;li&gt;Back up PostgreSQL and the encryption key; perform periodic restore drills.&lt;/li&gt;
&lt;li&gt;Clone production configuration into a non-production environment and run representative workflows.&lt;/li&gt;
&lt;li&gt;Pin the new image, run database migrations through the documented n8n process, and deploy compatible roles in a controlled order.&lt;/li&gt;
&lt;li&gt;Pause or drain work if the release requires it. Observe synthetic and real executions.&lt;/li&gt;
&lt;li&gt;Roll back application images only when the database migration is backward compatible. Otherwise restore the pre-upgrade database and matching encryption key according to the tested plan.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="alternatives"&gt;
&lt;a href="#alternatives" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Alternatives
&lt;/h2&gt;
&lt;p&gt;n8n Cloud removes most infrastructure ownership. A single VM/Compose deployment is often simpler for personal workloads and can be reliable with backups and downtime tolerance. ECS fits teams already operating AWS container workloads. EKS adds Kubernetes ecosystem capabilities but also operational cost. Choose based on recovery objectives, concurrency, expertise, and compliance—not the word “serverless.”&lt;/p&gt;
&lt;p&gt;Verified on &lt;strong&gt;2026-08-01&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="primary-sources"&gt;
&lt;a href="#primary-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Primary sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.n8n.io/hosting/scaling/queue-mode/"&gt;n8n queue mode&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.n8n.io/hosting/scaling/execution-data/"&gt;n8n execution data and pruning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.n8n.io/hosting/configuration/task-runners/"&gt;n8n task runners&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/deployment-circuit-breaker.html"&gt;Amazon ECS deployment rollback&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Kiro After General Availability: Specs, Agents, CLI, and Web</title><link>https://www.yopa.page/blog/2025-07-22-kiro-ide-the-smart-coding-tool-that-plans-before-it-builds.html</link><pubDate>Tue, 22 Jul 2025 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2025-07-22-kiro-ide-the-smart-coding-tool-that-plans-before-it-builds.html</guid><description>
&lt;p&gt;Kiro launched in preview in July 2025 and reached general availability in November 2025. It is no longer just a preview IDE with “vibe” and “spec” modes. The product now spans IDE and CLI workflows, team features, property-based testing, checkpoints, remote MCP, global steering, custom and parallel agents, and Kiro Web preview. Availability, models, credit accounting, and prices change, so use the live product and pricing pages for purchasing decisions.&lt;/p&gt;
&lt;h2 id="choose-the-interaction-by-task"&gt;
&lt;a href="#choose-the-interaction-by-task" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Choose the interaction by task
&lt;/h2&gt;
&lt;p&gt;Use &lt;strong&gt;specs&lt;/strong&gt; when the work needs explicit requirements, design, tasks, and traceability. They help when a feature crosses components or when a team needs to review intent before implementation. A spec is not proof that the requirement is correct; review acceptance criteria, data, security, migration, and non-goals.&lt;/p&gt;
&lt;p&gt;Use &lt;strong&gt;agentic chat&lt;/strong&gt; for bounded exploration, diagnosis, and small edits where a full spec would add ceremony. Ask it to inspect current repository facts first and require changed-file and validation evidence.&lt;/p&gt;
&lt;p&gt;Use &lt;strong&gt;Kiro CLI&lt;/strong&gt; when terminal context, scripts, or remote environments matter. Use &lt;strong&gt;Kiro Web&lt;/strong&gt; for asynchronous repository work and pull-request creation when its preview status and connected-repository permissions fit the task. Keep branch protection and human review; a generated PR is not an approval.&lt;/p&gt;
&lt;h2 id="persistent-project-context"&gt;
&lt;a href="#persistent-project-context" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Persistent project context
&lt;/h2&gt;
&lt;p&gt;Steering files capture durable conventions: architecture boundaries, build/test commands, naming, privacy, and release rules. Keep them short and version-controlled. Do not put secrets, customer data, personal preferences unrelated to the project, or frequently changing status in steering.&lt;/p&gt;
&lt;p&gt;Hooks automate actions around events. They consume credits and can amplify mistakes, so start with read-only checks or formatting/tests. Avoid hooks that deploy, publish, delete, or broadly rewrite without an explicit approval boundary.&lt;/p&gt;
&lt;p&gt;MCP connects tools and external context. Treat MCP servers as privileged software: review origin, permissions, data sent, authentication, and tool schemas. Remote content can contain prompt injection.&lt;/p&gt;
&lt;h2 id="a-reliable-workflow"&gt;
&lt;a href="#a-reliable-workflow" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A reliable workflow
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Open the intended repository/branch and inspect its instructions and dirty state.&lt;/li&gt;
&lt;li&gt;Write a small task brief with goal, non-goals, constraints, acceptance tests, and rollback.&lt;/li&gt;
&lt;li&gt;Use a spec for cross-cutting work; otherwise begin with a plan and targeted reads.&lt;/li&gt;
&lt;li&gt;Keep edits small and review checkpoints before high-impact steps.&lt;/li&gt;
&lt;li&gt;Run deterministic format, type, unit, integration, and build checks.&lt;/li&gt;
&lt;li&gt;Inspect the diff and test negative/security cases.&lt;/li&gt;
&lt;li&gt;Commit to a branch and use normal CI/review/release controls.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Property-based testing can derive general properties from specs and generate many cases, which is valuable for input spaces. It complements example tests; it does not replace integration, security, usability, or production acceptance.&lt;/p&gt;
&lt;h2 id="tradeoffs-and-evaluation"&gt;
&lt;a href="#tradeoffs-and-evaluation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Tradeoffs and evaluation
&lt;/h2&gt;
&lt;p&gt;Specs improve alignment but can become stale or produce false confidence. Chat is fast but may drift across a large task. Parallel agents can reduce elapsed time when file ownership is disjoint, but increase conflicts and inconsistent assumptions. More tool access increases capability and risk.&lt;/p&gt;
&lt;p&gt;Evaluate Kiro on representative repository tasks: success rate, escaped defects, reviewer edit distance, latency, credit/cost, security violations, and ability to recover. Compare against the team&amp;rsquo;s current workflow. Do not choose based only on a polished greenfield demo.&lt;/p&gt;
&lt;h2 id="migration-from-the-preview-era-workflow"&gt;
&lt;a href="#migration-from-the-preview-era-workflow" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration from the preview-era workflow
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Remove waitlist and fixed preview-limit claims; link live pricing.&lt;/li&gt;
&lt;li&gt;Move durable repository guidance into reviewed steering files.&lt;/li&gt;
&lt;li&gt;Convert reusable high-risk hooks to manual commands or approval-gated automation.&lt;/li&gt;
&lt;li&gt;Review MCP servers and custom-agent permissions.&lt;/li&gt;
&lt;li&gt;Add checkpoints, deterministic validation, and branch/PR review.&lt;/li&gt;
&lt;li&gt;Re-evaluate IDE, CLI, and Web separately for data and repository access.&lt;/li&gt;
&lt;li&gt;Re-run a small benchmark when models or credit policies change.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Verified on &lt;strong&gt;2026-08-01&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="primary-sources"&gt;
&lt;a href="#primary-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Primary sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://kiro.dev/blog/general-availability/"&gt;Kiro general availability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kiro.dev/docs/"&gt;Kiro documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kiro.dev/blog/introducing-kiro-web/"&gt;Kiro Web&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kiro.dev/pricing/"&gt;Kiro pricing&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>AWS Vector Databases for RAG — A 2026 Architectural Decision Guide</title><link>https://www.yopa.page/blog/2025-05-25-aws-vector-databases-rag-applications-complete-architectural-decision-guide.html</link><pubDate>Sun, 25 May 2025 02:30:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2025-05-25-aws-vector-databases-rag-applications-complete-architectural-decision-guide.html</guid><description>
&lt;blockquote&gt;
&lt;p&gt;Choosing a vector store on AWS used to be a five-way decision. With &lt;strong&gt;Amazon S3 Vectors&lt;/strong&gt; in the picture, it&amp;rsquo;s now better framed as a question of &lt;em&gt;which tier of the cost/latency curve your workload belongs to&lt;/em&gt;. This is an updated decision framework — what each option is good at, when it wins, and how they increasingly combine instead of compete.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Update note (2026):&lt;/strong&gt; This post was first written in May 2025. I&amp;rsquo;ve since reworked it to add S3 Vectors, mark the services that have graduated from preview to general availability, and cut the date-stamped claims that aged badly. If you read the original five-service version, the biggest change is that &amp;ldquo;pick one database&amp;rdquo; is now mostly the wrong question.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="the-mental-model-a-costlatency-tier-not-a-beauty-contest"&gt;
&lt;a href="#the-mental-model-a-costlatency-tier-not-a-beauty-contest" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The mental model: a cost/latency tier, not a beauty contest
&lt;/h2&gt;
&lt;p&gt;Most &amp;ldquo;which vector DB should I use&amp;rdquo; guides line up services side by side as if you pick one and commit. In practice the AWS-native options now sort cleanly onto a &lt;strong&gt;cost vs. latency curve&lt;/strong&gt;:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;When it fits&lt;/th&gt;
&lt;th&gt;AWS option&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hot / real-time&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High QPS, sub-50ms p99, always-on&lt;/td&gt;
&lt;td&gt;OpenSearch Service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Warm / transactional&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Vectors next to relational data, ACID&lt;/td&gt;
&lt;td&gt;Aurora / RDS PostgreSQL + pgvector&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Cold / cost-first&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Billions of vectors, infrequent or batch queries&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;S3 Vectors&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Managed / fastest-to-ship&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;You want RAG, not a database&lt;/td&gt;
&lt;td&gt;Bedrock Knowledge Bases&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Graph-aware&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Relationships matter as much as similarity&lt;/td&gt;
&lt;td&gt;Neptune Analytics (GraphRAG)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Turnkey enterprise search&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Connectors + access control, zero vector expertise&lt;/td&gt;
&lt;td&gt;Kendra GenAI Index&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The interesting shift since 2025 is that the top and bottom of this table now &lt;strong&gt;tier together&lt;/strong&gt;: you keep hot vectors in OpenSearch and spill the long tail into S3 Vectors, instead of paying memory prices for cold data. More on that below.&lt;/p&gt;
&lt;h2 id="amazon-s3-vectors--the-cost-floor-the-big-addition"&gt;
&lt;a href="#amazon-s3-vectors--the-cost-floor-the-big-addition" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Amazon S3 Vectors — the cost floor (the big addition)
&lt;/h2&gt;
&lt;p&gt;This is the service that didn&amp;rsquo;t exist when I first wrote this guide, and it changes the economics enough to deserve top billing.&lt;/p&gt;
&lt;p&gt;S3 Vectors is the first object store with &lt;strong&gt;native vector storage and query&lt;/strong&gt; — you create vector indexes inside S3 and query them through a dedicated API, with no cluster to provision. AWS positions it as reducing the cost of uploading, storing, and querying vectors &lt;strong&gt;by up to 90%&lt;/strong&gt; versus running an always-on vector database.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key facts:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Scale:&lt;/strong&gt; up to &lt;strong&gt;2 billion vectors per index&lt;/strong&gt;, 10,000 indexes per vector bucket — into the trillions of vectors per bucket.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Latency:&lt;/strong&gt; warm queries land around &lt;strong&gt;~100ms&lt;/strong&gt; (sub-second), not the single-digit milliseconds of an in-memory engine. This is the tradeoff.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistency:&lt;/strong&gt; strong consistency — a query always sees your most recently written vectors.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;No infrastructure:&lt;/strong&gt; pay-for-what-you-use, zero provisioning.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Where it fits:&lt;/strong&gt; large, long-lived vector datasets that are queried infrequently or in batch — agent long-term memory, archival semantic search, &amp;ldquo;search everything we&amp;rsquo;ve ever ingested&amp;rdquo; corpora. It is explicitly &lt;em&gt;not&lt;/em&gt; the choice for high-QPS real-time serving.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The integration that matters:&lt;/strong&gt; S3 Vectors is natively wired into &lt;strong&gt;Bedrock Knowledge Bases&lt;/strong&gt; (so a KB can use S3 Vectors as its store and cut RAG cost), and into &lt;strong&gt;OpenSearch Service&lt;/strong&gt; for a tiered strategy — hot vectors in OpenSearch for low latency, the cold bulk in S3 Vectors for cost. That tiering is the single most useful architectural pattern to come out of this update.&lt;/p&gt;
&lt;h2 id="amazon-opensearch-service--the-scale-and-latency-champion"&gt;
&lt;a href="#amazon-opensearch-service--the-scale-and-latency-champion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Amazon OpenSearch Service — the scale and latency champion
&lt;/h2&gt;
&lt;p&gt;OpenSearch remains the answer when you need &lt;strong&gt;high QPS at sub-second (often single-digit-ms) latency&lt;/strong&gt; over very large vector counts. It&amp;rsquo;s the most operationally flexible option and the most proven at billion-scale.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Strengths:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Multiple engines (FAISS, Lucene, NMSLIB) with HNSW, IVF, and product quantization.&lt;/li&gt;
&lt;li&gt;Aggressive compression: binary quantization (~32x) and product quantization, plus disk-optimized ANN that cuts memory needs substantially.&lt;/li&gt;
&lt;li&gt;Serverless option with pay-per-OCU autoscaling for spiky traffic.&lt;/li&gt;
&lt;li&gt;Metadata filtering integrated with k-NN for multi-tenancy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Tradeoff:&lt;/strong&gt; it&amp;rsquo;s a search cluster — you own the operational complexity and the memory bill. With S3 Vectors now available as a cold tier, the right move is increasingly &lt;em&gt;OpenSearch for the hot set, S3 Vectors for the rest&lt;/em&gt; rather than sizing OpenSearch for your entire corpus.&lt;/p&gt;
&lt;h2 id="aurora--rds-postgresql--pgvector--the-integration-master"&gt;
&lt;a href="#aurora--rds-postgresql--pgvector--the-integration-master" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Aurora / RDS PostgreSQL + pgvector — the integration master
&lt;/h2&gt;
&lt;p&gt;If your vectors live next to relational data, &lt;code&gt;pgvector&lt;/code&gt; is the path of least resistance: vector search and ACID transactions in one engine, with familiar tooling.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Strengths:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;HNSW indexes, &lt;code&gt;halfvec&lt;/code&gt; (50% memory reduction), sparse and binary vector types.&lt;/li&gt;
&lt;li&gt;Iterative index scans (0.8.x) that prevent over-filtering when you combine &lt;code&gt;WHERE&lt;/code&gt; clauses with vector search.&lt;/li&gt;
&lt;li&gt;Graviton instances for better price/performance; Aurora Serverless v2 scales down for variable load.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Tradeoff:&lt;/strong&gt; HNSW indexes want to live in memory, so instance sizing gets critical past a few million vectors. Best when relational + vector in one transactional store is worth more than peak vector throughput.&lt;/p&gt;
&lt;h2 id="amazon-bedrock-knowledge-bases--fastest-path-to-rag"&gt;
&lt;a href="#amazon-bedrock-knowledge-bases--fastest-path-to-rag" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Amazon Bedrock Knowledge Bases — fastest path to RAG
&lt;/h2&gt;
&lt;p&gt;If what you actually want is &lt;em&gt;retrieval-augmented generation&lt;/em&gt; and not a vector database to operate, Bedrock Knowledge Bases is the managed front door: ingest → chunk → embed → retrieve → (optionally) generate, with the vector store abstracted away.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What&amp;rsquo;s matured since 2025:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;It can sit on multiple backing stores — OpenSearch Serverless, Aurora pgvector, Neptune Analytics, and now &lt;strong&gt;S3 Vectors&lt;/strong&gt; for cost-optimized RAG.&lt;/li&gt;
&lt;li&gt;Multimodal ingestion, structured-data (natural-language-to-SQL) retrieval, reranking, custom chunking, and built-in RAG evaluation have moved from &amp;ldquo;preview&amp;rdquo; toward standard features.&lt;/li&gt;
&lt;li&gt;GraphRAG (via Neptune Analytics) is available as a retrieval mode for relationship-rich corpora.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Tradeoff:&lt;/strong&gt; less control over the retrieval internals. The migration path is the point: start on Bedrock KB to validate, drop to a self-managed store only when you&amp;rsquo;ve proven a specific need it can&amp;rsquo;t meet.&lt;/p&gt;
&lt;h2 id="amazon-neptune-analytics--the-relationship-expert"&gt;
&lt;a href="#amazon-neptune-analytics--the-relationship-expert" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Amazon Neptune Analytics — the relationship expert
&lt;/h2&gt;
&lt;p&gt;Neptune Analytics combines vector similarity with &lt;strong&gt;graph traversal&lt;/strong&gt;, powering GraphRAG: multi-hop, relationship-aware retrieval that plain similarity search can&amp;rsquo;t do. For knowledge graphs and densely interconnected documents it materially improves answer quality on questions that depend on &lt;em&gt;connections&lt;/em&gt;, not just nearest neighbors.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tradeoff:&lt;/strong&gt; higher modeling complexity than pure vector search, and it only pays off when your data genuinely has rich relationships. If &amp;ldquo;find similar chunks&amp;rdquo; is enough, this is overkill.&lt;/p&gt;
&lt;h2 id="amazon-kendra-genai-index--turnkey-enterprise-search"&gt;
&lt;a href="#amazon-kendra-genai-index--turnkey-enterprise-search" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Amazon Kendra GenAI Index — turnkey enterprise search
&lt;/h2&gt;
&lt;p&gt;Kendra delivers semantic enterprise search with &lt;strong&gt;no vector expertise required&lt;/strong&gt;: managed connectors to enterprise sources, built-in document understanding, and native access control. The GenAI Index is tuned for RAG retrieval and can also feed Bedrock Knowledge Bases.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tradeoff:&lt;/strong&gt; the least flexible and not the cheapest at small scale, but the fastest time-to-value for &amp;ldquo;search our internal documents, respecting who&amp;rsquo;s allowed to see what.&amp;rdquo;&lt;/p&gt;
&lt;h2 id="decision-shortcuts"&gt;
&lt;a href="#decision-shortcuts" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Decision shortcuts
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;High QPS, real-time, large scale&lt;/strong&gt; → OpenSearch (spill cold vectors to S3 Vectors).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Billions of vectors, infrequent/batch queries, cost is the constraint&lt;/strong&gt; → S3 Vectors.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Vectors alongside relational data, need ACID&lt;/strong&gt; → Aurora/RDS pgvector.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;You want RAG shipped this week, not a database to run&lt;/strong&gt; → Bedrock Knowledge Bases.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Answers depend on relationships, not just similarity&lt;/strong&gt; → Neptune Analytics (GraphRAG).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enterprise document search with access control, no ML team&lt;/strong&gt; → Kendra GenAI Index.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="anti-patterns-to-avoid"&gt;
&lt;a href="#anti-patterns-to-avoid" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Anti-patterns to avoid
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Sizing one hot store for your entire corpus.&lt;/strong&gt; This was the default in 2025 because there was no good cold tier. With S3 Vectors, paying in-memory prices for rarely-queried vectors is now a choice, not a necessity — tier instead.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Over-engineering the first deployment.&lt;/strong&gt; If Bedrock Knowledge Bases gets you to a working RAG demo, start there and migrate only when you&amp;rsquo;ve proven a concrete limitation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Forcing every data type into one service.&lt;/strong&gt; Hybrid is normal: hot content in OpenSearch, cold bulk in S3 Vectors, relationships in Neptune, structured data in pgvector. Pick per data shape, not per dogma.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Skipping quantization.&lt;/strong&gt; Running full float32 vectors uncompressed wastes memory and money; binary/product quantization usually preserves quality at a fraction of the footprint.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Cheers! 🍺&lt;/p&gt;</description></item><item><title>A Practical Taxonomy for AI Agent Systems</title><link>https://www.yopa.page/blog/2026-08-01-a-practical-taxonomy-for-ai-agent-systems.html</link><pubDate>Wed, 21 May 2025 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-a-practical-taxonomy-for-ai-agent-systems.html</guid><description>
&lt;p&gt;“AI agent” and “agentic AI” do not have one universally enforced boundary. Vendors and researchers use the terms differently. A system does not become more capable merely because it is called agentic, and a multi-agent topology is not automatically more autonomous than one agent.&lt;/p&gt;
&lt;p&gt;For design and governance, describe observable capabilities instead of choosing between two labels.&lt;/p&gt;
&lt;h2 id="six-dimensions-that-matter"&gt;
&lt;a href="#six-dimensions-that-matter" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Six dimensions that matter
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Tool authority:&lt;/strong&gt; Can the model only retrieve information, or can it write data, send messages, deploy software, move money, or delete resources?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Planning horizon:&lt;/strong&gt; Does it answer one turn, execute a bounded workflow, or pursue a goal across hours and resumptions?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Autonomy:&lt;/strong&gt; Which steps require human approval? Can the system choose new goals, or only methods inside an approved goal?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;State and memory:&lt;/strong&gt; Is context limited to one request, persisted in a session, or extracted into long-term user or organizational memory?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Delegation topology:&lt;/strong&gt; Is there one model/tool loop, a supervisor with specialists, peer-to-peer delegation, or a deterministic workflow calling models?&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Environment uncertainty:&lt;/strong&gt; Are actions applied to a sandbox, a reversible draft, or a live system with people and money?&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Two systems with the same “agent” label can have completely different risk. A single agent with permission to refund customers is more consequential than five read-only agents summarizing documents.&lt;/p&gt;
&lt;h2 id="choose-the-smallest-architecture"&gt;
&lt;a href="#choose-the-smallest-architecture" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Choose the smallest architecture
&lt;/h2&gt;
&lt;p&gt;Start with a deterministic application calling a model for a narrow transformation. Add retrieval when current or private knowledge is required. Add tools when the model must act, but expose specific operations with validated inputs rather than a general shell or database credential. Add planning only when the task cannot be represented as a known workflow.&lt;/p&gt;
&lt;p&gt;Use multiple agents when specialization creates a measurable benefit: separate context ownership, independently evaluated skills, security boundaries, or parallel work with disjoint outputs. Do not add agents just to imitate an organization chart. Every delegation adds latency, tokens, failure modes, context loss, and another authorization decision.&lt;/p&gt;
&lt;p&gt;For reliable business processes, a workflow engine should usually own durable state, retries, deadlines, compensation, and human approvals. A model can recommend or execute a bounded step without becoming the system of record.&lt;/p&gt;
&lt;h2 id="a-decision-record"&gt;
&lt;a href="#a-decision-record" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A decision record
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;goal&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;triage incoming support cases&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;read_case&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;draft_reply&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;forbidden&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;send_reply&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;change_account&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;planning_horizon&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;one_case&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;memory&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;session_only&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;human_gate&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;approve_draft&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;rollback&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;discard_draft&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;success_metrics&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;routing_accuracy&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;unsafe_action_rate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;reviewer_edit_distance&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This description is testable. “Build an agentic support system” is not.&lt;/p&gt;
&lt;h2 id="security-and-evaluation"&gt;
&lt;a href="#security-and-evaluation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Security and evaluation
&lt;/h2&gt;
&lt;p&gt;Treat prompts, retrieved documents, web pages, tool output, and messages from other agents as untrusted data. Validate tool arguments server-side and authorize every action against the authenticated actor and current resource—not against text that says the user is allowed. Use least-privilege workload identity, egress controls, secret isolation, audit logs, cost/time budgets, and idempotency keys.&lt;/p&gt;
&lt;p&gt;Evaluate each layer separately:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;model quality on representative inputs;&lt;/li&gt;
&lt;li&gt;retrieval recall and grounding;&lt;/li&gt;
&lt;li&gt;tool selection and argument validity;&lt;/li&gt;
&lt;li&gt;authorization denials and prompt-injection resistance;&lt;/li&gt;
&lt;li&gt;end-to-end task success, latency, cost, and recoverability;&lt;/li&gt;
&lt;li&gt;human escalation quality when confidence or policy is unclear.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Test duplicate events, partial failures, stale context, unavailable tools, malicious retrieved text, and a delegated agent returning an invalid result. A polished demo is not evidence of safe autonomy.&lt;/p&gt;
&lt;h2 id="migration-from-label-first-architecture"&gt;
&lt;a href="#migration-from-label-first-architecture" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration from label-first architecture
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Inventory every tool and classify its read/write and reversibility.&lt;/li&gt;
&lt;li&gt;Record planning horizon, memory scope, identity propagation, and approval points.&lt;/li&gt;
&lt;li&gt;Replace broad tool access with narrow typed operations.&lt;/li&gt;
&lt;li&gt;Move durable workflow state out of model context.&lt;/li&gt;
&lt;li&gt;Establish a single-agent baseline before adding delegation.&lt;/li&gt;
&lt;li&gt;Add evaluation cases and explicit stop budgets.&lt;/li&gt;
&lt;li&gt;Roll out read-only, then drafts, then narrowly approved actions.&lt;/li&gt;
&lt;li&gt;Review whether each extra agent improves measured outcomes enough to justify complexity.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Verified on &lt;strong&gt;2026-08-01&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="primary-sources"&gt;
&lt;a href="#primary-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Primary sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.nist.gov/itl/ai-risk-management-framework"&gt;NIST AI Risk Management Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://genai.owasp.org/llm-top-10/"&gt;OWASP Top 10 for LLM Applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://a2a-protocol.org/latest/specification/"&gt;Agent2Agent protocol specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/specification/"&gt;Model Context Protocol specification&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Cognito and Salesforce OIDC Trust Topologies</title><link>https://www.yopa.page/blog/2026-08-01-cognito-salesforce-oidc-trust-topologies.html</link><pubDate>Fri, 11 Apr 2025 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-cognito-salesforce-oidc-trust-topologies.html</guid><description>
&lt;p&gt;“SSO between Cognito and Salesforce” describes two opposite trust directions. Configuration fails when the identity provider (IdP), relying party (RP), token issuer, and callback owner change halfway through the design. Choose one topology from the user journey and write it down before opening either console.&lt;/p&gt;
&lt;p&gt;This guide was verified on 2026-08-01 against the official AWS documentation for &lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-oidc-idp.html"&gt;Cognito user pools with OIDC providers&lt;/a&gt;, &lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/federation-endpoints.html"&gt;Cognito federation endpoints&lt;/a&gt;, and &lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-security-best-practices.html"&gt;Cognito security practices&lt;/a&gt;, plus Salesforce guidance for &lt;a href="https://developer.salesforce.com/docs/platform/accsdk/guide/acc-sdk-setup-auth-external.html"&gt;External Client Apps&lt;/a&gt; and &lt;a href="https://help.salesforce.com/s/articleView?id=xcloud.security_auth_slo_oidc_idp_configuring.htm&amp;amp;language=en_US&amp;amp;type=5"&gt;OIDC single logout&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="topology-a-cognito-is-the-idp-salesforce-is-the-rp"&gt;
&lt;a href="#topology-a-cognito-is-the-idp-salesforce-is-the-rp" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Topology A: Cognito is the IdP, Salesforce is the RP
&lt;/h2&gt;
&lt;p&gt;Use this when users should enter Salesforce with an identity represented by a Cognito user pool. Salesforce creates an OpenID Connect Authentication Provider that trusts Cognito. The browser starts at Salesforce, goes to the Cognito authorization endpoint, and returns to the exact callback URL generated by Salesforce.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Give the user pool a domain and an app client using the authorization-code grant.&lt;/li&gt;
&lt;li&gt;In Salesforce, create an OpenID Connect Auth Provider. Copy its generated callback URL exactly into the Cognito app client&amp;rsquo;s allowed callback URLs.&lt;/li&gt;
&lt;li&gt;Configure Salesforce from Cognito&amp;rsquo;s discovery metadata rather than guessing endpoint paths. Cognito publishes discovery under its issuer and signing keys through JWKS. Cache keys by &lt;code&gt;kid&lt;/code&gt;, refresh them on an unknown key, and validate issuer, audience, signature, expiry, and nonce.&lt;/li&gt;
&lt;li&gt;Request only needed scopes, normally &lt;code&gt;openid&lt;/code&gt; plus approved profile claims.&lt;/li&gt;
&lt;li&gt;Implement a Salesforce Registration Handler that maps an immutable external subject to an existing user or performs tightly controlled just-in-time (JIT) provisioning.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The Cognito app client is confidential when Salesforce authenticates to the token endpoint with a secret. Store that secret only in the platform configuration intended for the Auth Provider. Do not put it in browser code, source control, screenshots, or an Apex custom setting readable by ordinary users.&lt;/p&gt;
&lt;p&gt;The Registration Handler must not match accounts by an unverified, mutable email address alone. Prefer the tuple of trusted issuer and &lt;code&gt;sub&lt;/code&gt;, preserve that external identifier, allowlist acceptable tenant or group claims, and apply least-privilege profiles and permission sets. Decide what happens when a user is disabled at the source; JIT creation without deprovisioning produces stale access.&lt;/p&gt;
&lt;h2 id="topology-b-salesforce-is-the-idp-cognito-is-the-rpbroker"&gt;
&lt;a href="#topology-b-salesforce-is-the-idp-cognito-is-the-rpbroker" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Topology B: Salesforce is the IdP, Cognito is the RP/broker
&lt;/h2&gt;
&lt;p&gt;Use this when a Salesforce identity should sign users into an application that receives Cognito-issued tokens. Salesforce authorizes the Cognito OIDC client, Cognito validates Salesforce tokens and maps claims, then Cognito issues its own user-pool tokens to the application.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Configure My Domain and create a Salesforce &lt;strong&gt;External Client App&lt;/strong&gt; for new OAuth integrations where Salesforce is the authorization server. Enable the authorization-code flow and the policy controls required by the org.&lt;/li&gt;
&lt;li&gt;Register Cognito&amp;rsquo;s exact OIDC callback as the Salesforce callback. For a Cognito user-pool OIDC provider, AWS documents the callback in the form &lt;code&gt;https://&amp;lt;user-pool-domain&amp;gt;/oauth2/idpresponse&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;In Cognito, add Salesforce as an OIDC IdP. Use the Salesforce My Domain discovery document: &lt;code&gt;https://&amp;lt;my-domain&amp;gt;.my.salesforce.com/.well-known/openid-configuration&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Confirm that discovery returns HTTPS authorization, token, user-info, and JWKS endpoints compatible with Cognito. AWS currently requires the provider&amp;rsquo;s supported client authentication behavior described in its OIDC IdP prerequisites.&lt;/li&gt;
&lt;li&gt;Map Salesforce claims to mutable and required Cognito attributes deliberately. Cognito automatically maps the upstream OIDC &lt;code&gt;sub&lt;/code&gt; into its federated identity; do not overwrite identity keys with display names.&lt;/li&gt;
&lt;li&gt;Enable this IdP for the intended Cognito app client and initiate sign-in through Cognito managed login or its authorize endpoint. The application accepts Cognito tokens, not upstream Salesforce tokens.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;External Client App policies should restrict who may authorize, scopes, session behavior, refresh-token use, and administrative approval. A public browser or native client must use authorization code with PKCE and must not contain a client secret. In this topology Cognito acts as a server-side OIDC client to Salesforce, so confirm the exact client-authentication method supported by both products rather than copying a public-client recipe.&lt;/p&gt;
&lt;h2 id="browser-request-controls"&gt;
&lt;a href="#browser-request-controls" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Browser request controls
&lt;/h2&gt;
&lt;p&gt;For application-to-Cognito authorization requests, use authorization code with PKCE (&lt;code&gt;S256&lt;/code&gt;). Generate a high-entropy &lt;code&gt;state&lt;/code&gt; value, bind it to the browser transaction, and compare it on return to prevent login CSRF. Generate and validate &lt;code&gt;nonce&lt;/code&gt; for OIDC replay protection. Use exact HTTPS redirect URIs; do not allow wildcards, substring matching, or user-provided return origins.&lt;/p&gt;
&lt;p&gt;Keep the post-login destination separate from &lt;code&gt;redirect_uri&lt;/code&gt;. Store an allowlisted relative route server-side or in integrity-protected state. Never redirect directly to an arbitrary &lt;code&gt;startURL&lt;/code&gt;, RelayState, or query-string URL supplied by a caller.&lt;/p&gt;
&lt;h2 id="claims-jit-and-authorization"&gt;
&lt;a href="#claims-jit-and-authorization" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Claims, JIT, and authorization
&lt;/h2&gt;
&lt;p&gt;OIDC proves an authentication event; it does not define the user&amp;rsquo;s application permissions. Document each required claim, its issuer, data type, mutability, and destination. Treat groups and roles as authorization input only after verifying the issuer and applying an allowlist. Minimize personally identifiable claims and avoid putting sensitive internal attributes in tokens visible to the browser.&lt;/p&gt;
&lt;p&gt;Test existing-user linking, first login, changed email, duplicate email, disabled user, missing claim, unexpected tenant, and a user with no authorized role. JIT provisioning should fail closed and produce an auditable event. Define deprovisioning through lifecycle automation, short sessions, token revocation, or a combination appropriate to the risk.&lt;/p&gt;
&lt;h2 id="logout-is-a-separate-protocol-decision"&gt;
&lt;a href="#logout-is-a-separate-protocol-decision" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Logout is a separate protocol decision
&lt;/h2&gt;
&lt;p&gt;Clearing the local application cookie does not end the Cognito, Salesforce, or upstream corporate IdP session. Define which sessions a “Log out” action should end. Cognito exposes a managed-login logout endpoint; Salesforce documents front-channel OIDC single logout when Salesforce is the provider. Product support is not symmetric, so test RP-initiated logout, IdP-initiated logout where supported, post-logout redirect allowlists, refresh-token revocation, and login from a shared browser.&lt;/p&gt;
&lt;h2 id="migration-and-verification-checklist"&gt;
&lt;a href="#migration-and-verification-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration and verification checklist
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Draw browser, app, Cognito, and Salesforce with arrows for authorize, callback, token, user-info, and logout.&lt;/li&gt;
&lt;li&gt;Record issuer, discovery URL, JWKS URL, client owner, exact callbacks, scopes, and secret owner.&lt;/li&gt;
&lt;li&gt;Start in non-production orgs and use test accounts with no privileged profile.&lt;/li&gt;
&lt;li&gt;Verify PKCE, &lt;code&gt;state&lt;/code&gt;, &lt;code&gt;nonce&lt;/code&gt;, issuer, audience, expiry, signature, and key rotation.&lt;/li&gt;
&lt;li&gt;Review claim mappings and JIT/deprovisioning with Salesforce and identity administrators.&lt;/li&gt;
&lt;li&gt;Test callback mismatch, invalid state/nonce, expired code, unknown &lt;code&gt;kid&lt;/code&gt;, disabled user, logout, and rollback.&lt;/li&gt;
&lt;li&gt;Capture audit logs from Cognito, Salesforce Login History, and the application without logging codes, tokens, or secrets.&lt;/li&gt;
&lt;li&gt;Promote exact configuration through infrastructure or change control; do not retype production callbacks from memory.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The acceptance gate is a complete browser trace in the chosen topology, least-privilege authorization after login, predictable logout, and a documented rollback that disables the new trust without locking out administrators.&lt;/p&gt;</description></item><item><title>Ruby Syntax and Core Collection Methods for Beginners</title><link>https://www.yopa.page/blog/2025-01-31-getting-started-with-ruby-on-rails-syntax-and-built-in-methods.html</link><pubDate>Fri, 31 Jan 2025 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2025-01-31-getting-started-with-ruby-on-rails-syntax-and-built-in-methods.html</guid><description>
&lt;h1 id="ruby-syntax-and-built-in-methods"&gt;
&lt;a href="#ruby-syntax-and-built-in-methods" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Ruby Syntax and Built-in Methods
&lt;/h1&gt;
&lt;p&gt;This is a Ruby language reference, not a Rails tutorial. Rails builds on Ruby, but it adds its own framework conventions, APIs, and lifecycle.&lt;/p&gt;
&lt;p&gt;The examples were reviewed on 2026-08-01 against the &lt;a href="https://docs.ruby-lang.org/en/3.4/syntax_rdoc.html"&gt;official Ruby syntax documentation&lt;/a&gt; and &lt;a href="https://www.ruby-lang.org/en/documentation/quickstart/"&gt;Ruby in Twenty Minutes&lt;/a&gt;. Check the &lt;a href="https://www.ruby-lang.org/en/documentation/"&gt;official Ruby documentation index&lt;/a&gt; and maintenance status before selecting a version for a new project. The examples use syntax available in maintained modern Ruby releases; pin the project&amp;rsquo;s version in development and CI rather than relying on the macOS system Ruby.&lt;/p&gt;
&lt;h2 id="table-of-contents"&gt;
&lt;a href="#table-of-contents" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Table of Contents
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href="./blog/2025-01-31-getting-started-with-ruby-on-rails-syntax-and-built-in-methods.html#introduction"&gt;Introduction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="./blog/2025-01-31-getting-started-with-ruby-on-rails-syntax-and-built-in-methods.html#class-definitions"&gt;Class Definitions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="./blog/2025-01-31-getting-started-with-ruby-on-rails-syntax-and-built-in-methods.html#inheritance"&gt;Inheritance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="./blog/2025-01-31-getting-started-with-ruby-on-rails-syntax-and-built-in-methods.html#function-definitions"&gt;Function Definitions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="./blog/2025-01-31-getting-started-with-ruby-on-rails-syntax-and-built-in-methods.html#variables-and-scope"&gt;Variables and Scope&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="./blog/2025-01-31-getting-started-with-ruby-on-rails-syntax-and-built-in-methods.html#loops-and-iteration"&gt;Loops and Iteration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="./blog/2025-01-31-getting-started-with-ruby-on-rails-syntax-and-built-in-methods.html#common-default-functions"&gt;Common Default Functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="./blog/2025-01-31-getting-started-with-ruby-on-rails-syntax-and-built-in-methods.html#built-in-methods"&gt;Built-in Methods&lt;/a&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="./blog/2025-01-31-getting-started-with-ruby-on-rails-syntax-and-built-in-methods.html#string-methods"&gt;String Methods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="./blog/2025-01-31-getting-started-with-ruby-on-rails-syntax-and-built-in-methods.html#array-methods"&gt;Array Methods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="./blog/2025-01-31-getting-started-with-ruby-on-rails-syntax-and-built-in-methods.html#hash-methods"&gt;Hash Methods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="./blog/2025-01-31-getting-started-with-ruby-on-rails-syntax-and-built-in-methods.html#numeric-methods"&gt;Numeric Methods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="./blog/2025-01-31-getting-started-with-ruby-on-rails-syntax-and-built-in-methods.html#enumerable-methods"&gt;Enumerable Methods&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="introduction"&gt;
&lt;a href="#introduction" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Introduction
&lt;/h2&gt;
&lt;p&gt;Ruby is an object-oriented language in which expressions return values and blocks are central to collection APIs. The Python comparisons below are orientation aids, not claims that the two object models are identical.&lt;/p&gt;
&lt;h2 id="class-definitions"&gt;
&lt;a href="#class-definitions" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Class Definitions
&lt;/h2&gt;
&lt;h3 id="ruby-syntax"&gt;
&lt;a href="#ruby-syntax" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Ruby Syntax
&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="vi"&gt;@name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt; &lt;span class="c1"&gt;# Instance variable; note that Ruby uses `@` to denote instance variables&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="vi"&gt;@age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;introduce&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Hi, my name is &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; and I am &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@age&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; years old.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Usage&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;introduce&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="python-equivalent"&gt;
&lt;a href="#python-equivalent" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Python Equivalent
&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;introduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Hi, my name is &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; and I am &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; years old.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Usage&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;person&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;introduce&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Ruby instance variables use an &lt;code&gt;@&lt;/code&gt; prefix. Python stores comparable state through attributes on &lt;code&gt;self&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="inheritance"&gt;
&lt;a href="#inheritance" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Inheritance
&lt;/h2&gt;
&lt;h3 id="ruby-implementation"&gt;
&lt;a href="#ruby-implementation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Ruby Implementation
&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Employee&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;Person&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;initialize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Calls parent class&amp;#39;s constructor&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="vi"&gt;@position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="c1"&gt;# then we set the instance variable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;work&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is working as a &lt;/span&gt;&lt;span class="si"&gt;#{&lt;/span&gt;&lt;span class="vi"&gt;@position&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="python-implementation"&gt;
&lt;a href="#python-implementation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Python Implementation
&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;super&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;work&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; is working as a &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;.&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Ruby writes superclass inheritance with &lt;code&gt;&amp;lt;&lt;/code&gt;; Python places base classes in parentheses.&lt;/p&gt;
&lt;h2 id="function-definitions"&gt;
&lt;a href="#function-definitions" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Function Definitions
&lt;/h2&gt;
&lt;h3 id="ruby-methods"&gt;
&lt;a href="#ruby-methods" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Ruby Methods
&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Calculator&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Default parameter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;calc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;Calculator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 15&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 7 (uses default value)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="python-methods"&gt;
&lt;a href="#python-methods" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Python Methods
&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Calculator&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="c1"&gt;# Default parameter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;calc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Calculator&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 15&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;calc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 7 (uses default value)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="variables-and-scope"&gt;
&lt;a href="#variables-and-scope" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Variables and Scope
&lt;/h2&gt;
&lt;h3 id="ruby-variables"&gt;
&lt;a href="#ruby-variables" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Ruby Variables
&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;demonstrate_variables&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;local_var&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;I am local&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# Local variable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="vi"&gt;@instance_var&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Instance scope&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# Instance variable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="n"&gt;local_var&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="vi"&gt;@instance_var&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Ruby also has class variables (&lt;code&gt;@@name&lt;/code&gt;) and globals (&lt;code&gt;$name&lt;/code&gt;), but both introduce wider mutable state and should not be the default for ordinary application data. Prefer explicit objects, constants for immutable shared values, and class-instance variables when class-owned state is genuinely required.&lt;/p&gt;
&lt;h3 id="python-variables"&gt;
&lt;a href="#python-variables" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Python Variables
&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;demonstrate_variables&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;local_var&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;I am local&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# Local variable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;instance_var&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Instance scope&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# Instance variable&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Class variables in Python use class namespace&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Global variables use global keyword&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="loops-and-iteration"&gt;
&lt;a href="#loops-and-iteration" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Loops and Iteration
&lt;/h2&gt;
&lt;h3 id="ruby-loops"&gt;
&lt;a href="#ruby-loops" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Ruby Loops
&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Each iterator&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;].&lt;/span&gt;&lt;span class="n"&gt;each&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;number&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# For loop&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;number&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# While loop&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Until loop (Ruby-specific)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;until&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;puts&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;end&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="built-in-methods"&gt;
&lt;a href="#built-in-methods" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Built-in Methods
&lt;/h2&gt;
&lt;h3 id="string-methods"&gt;
&lt;a href="#string-methods" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
String Methods
&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Common string operations&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Hello, World!&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 13&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upcase&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; &amp;#34;HELLO, WORLD!&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;downcase&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; &amp;#34;hello, world!&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;capitalize&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; &amp;#34;Hello, world!&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reverse&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; &amp;#34;!dlroW ,olleH&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;,&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; [&amp;#34;Hello&amp;#34;, &amp;#34; World!&amp;#34;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="array-methods"&gt;
&lt;a href="#array-methods" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Array Methods
&lt;/h3&gt;
&lt;p&gt;Ruby arrays can contain objects of different classes, like Python lists. That flexibility is a Ruby language property and does not depend on Rails.&lt;/p&gt;
&lt;blockquote&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Mixed type array examples&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;mixed_array&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;hello&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kp"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;John&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# You can add different types at any time&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;mixed_array&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:symbol&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Adds a Symbol&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;mixed_array&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt; &lt;span class="c1"&gt;# Adds nil using the shovel operator&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;mixed_array&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unshift&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Adds a Float at the beginning&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# These operations are valid; mixed collections still require clear application contracts.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Array operations&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;arr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;length&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 5&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;include?&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Mutating operations change their receiver&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# arr =&amp;gt; [1, 2, 3, 4, 5, 6]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 6; arr =&amp;gt; [1, 2, 3, 4, 5]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;unshift&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# arr =&amp;gt; [0, 1, 2, 3, 4, 5]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shift&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 0; arr =&amp;gt; [1, 2, 3, 4, 5]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;99&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# arr =&amp;gt; [1, 2, 99, 3, 4, 5]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 99; arr =&amp;gt; [1, 2, 3, 4, 5]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# delete removes every matching value and returns the argument when found&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 0; values =&amp;gt; [1, 2]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# delete_if removes values matching the block&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete_if&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;even?&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# numbers =&amp;gt; [1, 3, 5]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# clear removes all values&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clear&lt;/span&gt; &lt;span class="c1"&gt;# numbers =&amp;gt; []&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="hash-methods"&gt;
&lt;a href="#hash-methods" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Hash Methods
&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Hash operations&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;John&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keys&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; [:name, :age]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; [&amp;#34;John&amp;#34;, 30]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:name&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; &amp;#34;John&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;city&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;New York&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; {:name=&amp;gt;&amp;#34;John&amp;#34;, :age=&amp;gt;30, :city=&amp;gt;&amp;#34;New York&amp;#34;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:age&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 30 # Deletes the key-value pair with the specified key and returns the value that was deleted&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Remove key-value pairs based on a condition&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;John&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;role&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Developer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;level&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Senior&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;delete_if&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;John&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; {:age=&amp;gt;30, :role=&amp;gt;&amp;#34;Developer&amp;#34;, :level=&amp;gt;&amp;#34;Senior&amp;#34;} # Removes all pairs where value is &amp;#34;John&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Remove nil values from hash&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;John&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;age&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="ss"&gt;department&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kp"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compact&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; {:name=&amp;gt;&amp;#34;John&amp;#34;, :age=&amp;gt;30} # Returns new hash with nil values removed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;compact!&lt;/span&gt; &lt;span class="c1"&gt;# Removes nil values from original hash permanently&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# how to remove all key-value pairs from the hash&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;clear&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; {} # Removes all key-value pairs from the hash&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="numeric-methods"&gt;
&lt;a href="#numeric-methods" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Numeric Methods
&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Numeric operations&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;even?&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;odd?&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;abs&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 42 # The - operator negates num to -42, then abs returns absolute value back to 42&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;round&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ceil&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;floor&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="enumerable-methods"&gt;
&lt;a href="#enumerable-methods" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Enumerable Methods
&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ruby" data-lang="ruby"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Collection operations&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Map/Collect&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;map&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; [2, 4, 6, 8, 10] # |n| is a block parameter, similar to a function parameter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# It represents each element from &amp;#39;numbers&amp;#39; as the map iterates&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# You can name it anything, e.g. |num| or |x|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Select/Filter&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;even?&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; [2, 4]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Reduce/Inject&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:+&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 15 # Adds all numbers in array: 1 + 2 + 3 + 4 + 5 = 15&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="ss"&gt;:+&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 15 # Same as reduce&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;element&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 15 # More explicit version where:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# - 0 is the initial value&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# - result is always the accumulated value from previous iterations&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# - element is the current array element being processed&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# The block parameters |result, element| follow a convention:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# - First parameter is always the accumulator/result&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# - Second parameter is always the current element&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Sort&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; [1, 2, 3, 4, 5] # Returns ascending order&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reverse&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; [5, 4, 3, 2, 1] # Sorts numbers in descending order&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Reverse&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reverse&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; [5, 4, 3, 2, 1] # Reverses the order of the array&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# First/Last&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; 1 # Returns the first element of the array&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Any/All&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;any?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; true # Returns true because at least one number (4,5) is greater than 3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;numbers&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;all?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;# =&amp;gt; true # Returns true because every number (1,2,3,4,5) is less than 6&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Use &lt;code&gt;ruby -c file.rb&lt;/code&gt; for a syntax check and run examples with the same Ruby version used by the project. For API details, consult the documentation for that exact release because core methods and keyword behavior can evolve across major versions.&lt;/p&gt;</description></item><item><title>Build a Privacy-Aware Meeting Transcription Workflow with Amazon Transcribe</title><link>https://www.yopa.page/blog/2025-01-24-unlocking-meeting-insights-with-amazon-transcribe.html</link><pubDate>Fri, 24 Jan 2025 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2025-01-24-unlocking-meeting-insights-with-amazon-transcribe.html</guid><description>
&lt;p&gt;Amazon Transcribe can turn recorded or streaming audio into text, with features such as speaker partitioning, custom vocabulary, and PII handling for supported languages and modes. The hard part of a meeting workflow is not starting a transcription job. It is deciding whether recording is permitted, protecting the audio and transcript, checking accuracy, and deleting data on schedule.&lt;/p&gt;
&lt;h2 id="establish-the-policy-first"&gt;
&lt;a href="#establish-the-policy-first" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Establish the policy first
&lt;/h2&gt;
&lt;p&gt;Recording and transcribing people can trigger consent, employment, contractual, privacy, and sector-specific requirements. Determine the applicable policy with the responsible legal/privacy owner. Notify participants in a form they can understand, provide an alternative where required, and do not silently repurpose transcripts for model training or performance monitoring.&lt;/p&gt;
&lt;p&gt;Define purpose, access, storage Region, retention, deletion, and downstream AI use before collection. A transcript is not less sensitive than audio; it may be easier to search and exfiltrate.&lt;/p&gt;
&lt;h2 id="a-bounded-batch-workflow"&gt;
&lt;a href="#a-bounded-batch-workflow" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A bounded batch workflow
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Upload audio to a private S3 input prefix using TLS and least-privilege access.&lt;/li&gt;
&lt;li&gt;Start a uniquely named Transcribe job with the correct language, media format, speaker settings, and optional vocabulary.&lt;/li&gt;
&lt;li&gt;Write output to a private destination you control rather than relying on a temporary service URL.&lt;/li&gt;
&lt;li&gt;Validate job status and output schema, then record a non-sensitive job ID.&lt;/li&gt;
&lt;li&gt;Run human review for names, numbers, decisions, and action items before treating the transcript as authoritative.&lt;/li&gt;
&lt;li&gt;Apply retention and deletion to audio, raw transcript, corrected transcript, summaries, logs, and backups.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;boto3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;uuid&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;transcribe&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;region_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;us-east-1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;job_name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;meeting-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;start_transcription_job&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;TranscriptionJobName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;job_name&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;LanguageCode&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;en-US&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;MediaFormat&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;mp4&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Media&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;MediaFileUri&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;s3://private-input/meeting.mp4&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;OutputBucketName&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;private-transcripts&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;ShowSpeakerLabels&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;MaxSpeakerLabels&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The caller role should access only the required prefixes and Transcribe actions. Use S3 Block Public Access, encryption, bucket policies, audit logging, and lifecycle rules. Do not put attendee names or meeting titles in public object keys or job names.&lt;/p&gt;
&lt;h2 id="summaries-and-genai"&gt;
&lt;a href="#summaries-and-genai" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Summaries and GenAI
&lt;/h2&gt;
&lt;p&gt;Amazon Transcribe Call Analytics supports additional call-focused insights and generative call summarization in supported scenarios. For ordinary meetings, a separate model may summarize a reviewed transcript, but first check service approval and data policy. Ground the summary in transcript segments and retain citations or timestamps. Ask the model to separate decisions, proposed ideas, owners, deadlines, and unresolved questions. Require a participant or meeting owner to approve consequential actions.&lt;/p&gt;
&lt;p&gt;PII redaction reduces some exposure; it is not anonymization and may miss context-specific identifiers. Do not send the raw transcript to arbitrary consumer AI tools. Minimize input to the segments needed for the task.&lt;/p&gt;
&lt;h2 id="accuracy-and-failure-modes"&gt;
&lt;a href="#accuracy-and-failure-modes" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Accuracy and failure modes
&lt;/h2&gt;
&lt;p&gt;ASR quality varies with accents, overlap, microphones, noise, domain terms, and language switching. Speaker labels distinguish channels or inferred speakers; they do not prove a person&amp;rsquo;s legal identity. Custom vocabularies can improve domain words but need evaluation.&lt;/p&gt;
&lt;p&gt;Test clipped audio, silent files, more speakers than configured, language mismatch, overlapping speech, failed jobs, duplicate uploads, and deletion failures. Make processing idempotent and alarm on jobs stuck or failed. Never equate a fluent summary with an exact record.&lt;/p&gt;
&lt;h2 id="alternatives"&gt;
&lt;a href="#alternatives" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Alternatives
&lt;/h2&gt;
&lt;p&gt;Local transcription may fit stricter data-boundary requirements but shifts model, device, security, and accuracy operations to the team. A meeting platform&amp;rsquo;s built-in transcription may offer better participant notice and speaker mapping. Transcribe is a good fit when AWS storage/integration, scale, and configurable processing matter.&lt;/p&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Add a consent and approved-use gate before upload.&lt;/li&gt;
&lt;li&gt;Replace broad AWS credentials with a role scoped to input/output prefixes.&lt;/li&gt;
&lt;li&gt;Enable private storage, encryption, lifecycle, audit, and deletion verification.&lt;/li&gt;
&lt;li&gt;Add speaker/language settings and representative accuracy tests.&lt;/li&gt;
&lt;li&gt;Review and correct transcripts before downstream automation.&lt;/li&gt;
&lt;li&gt;Add citations and human approval to summaries and actions.&lt;/li&gt;
&lt;li&gt;Delete old local transcript copies and revoke obsolete access.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Verified on &lt;strong&gt;2026-08-01&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="primary-sources"&gt;
&lt;a href="#primary-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Primary sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/transcribe/latest/dg/how-batch.html"&gt;Amazon Transcribe batch transcription&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/transcribe/latest/dg/diarization.html"&gt;Speaker partitioning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/transcribe/latest/dg/pii-redaction.html"&gt;PII redaction&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/transcribe/latest/dg/call-analytics.html"&gt;Call Analytics&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Debug AWS Amplify and Cognito Sign-In Flows</title><link>https://www.yopa.page/blog/2026-08-01-debug-amplify-cognito-sign-in-flows.html</link><pubDate>Mon, 30 Dec 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-debug-amplify-cognito-sign-in-flows.html</guid><description>
&lt;p&gt;Adding &lt;code&gt;authFlowType: &amp;quot;USER_PASSWORD_AUTH&amp;quot;&lt;/code&gt; is not a universal fix for an Amplify sign-in error. Cognito must allow that flow on the app client, and it sends the user&amp;rsquo;s password to the service over TLS rather than using Secure Remote Password (SRP). Amplify&amp;rsquo;s default SRP flow is usually the better starting point for username/password authentication.&lt;/p&gt;
&lt;p&gt;The reliable way to debug sign-in is to align four layers: Amplify configuration, Cognito app-client settings, the selected authentication flow, and the multi-step state machine returned by Amplify Auth.&lt;/p&gt;
&lt;h2 id="configure-without-embedding-secrets"&gt;
&lt;a href="#configure-without-embedding-secrets" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Configure without embedding secrets
&lt;/h2&gt;
&lt;p&gt;A public React Native or browser application is a &lt;strong&gt;public client&lt;/strong&gt;. It cannot protect a Cognito app-client secret. Create an app client without a secret and restrict callback/sign-out URLs and OAuth flows to the actual application.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Amplify&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aws-amplify&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Amplify&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Auth&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;Cognito&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;userPoolId&lt;/span&gt;: &lt;span class="kt"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;EXPO_PUBLIC_COGNITO_USER_POOL_ID&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;userPoolClientId&lt;/span&gt;: &lt;span class="kt"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;EXPO_PUBLIC_COGNITO_CLIENT_ID&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;signUpVerificationMethod&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;code&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Fail at startup when configuration is missing. A client ID and user pool ID are identifiers, not secrets, but do not log tokens, passwords, authorization codes, or full authentication responses.&lt;/p&gt;
&lt;h2 id="start-with-srp-and-handle-nextstep"&gt;
&lt;a href="#start-with-srp-and-handle-nextstep" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Start with SRP and handle &lt;code&gt;nextStep&lt;/code&gt;
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;signIn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;confirmSignIn&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aws-amplify/auth&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;signIn&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;password&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;nextStep&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;signInStep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;DONE&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;CONFIRM_SIGN_IN_WITH_TOTP_CODE&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;CONFIRM_SIGN_IN_WITH_SMS_CODE&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;CONFIRM_SIGN_IN_WITH_EMAIL_CODE&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Ask the user for the challenge response, then:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// await confirmSignIn({ challengeResponse: code });
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;CONFIRM_SIGN_IN_WITH_NEW_PASSWORD_REQUIRED&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Collect and validate the new password and required attributes.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;RESET_PASSWORD&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;CONFIRM_SIGN_UP&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Route to the corresponding recovery or confirmation flow.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Show a recoverable UI and record only a sanitized error code.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The exact &lt;code&gt;nextStep&lt;/code&gt; variants depend on the Amplify version and enabled Cognito features. Use the installed package types and current documentation rather than assuming every successful credential check returns &lt;code&gt;DONE&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="when-user_password_auth-is-justified"&gt;
&lt;a href="#when-user_password_auth-is-justified" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
When &lt;code&gt;USER_PASSWORD_AUTH&lt;/code&gt; is justified
&lt;/h2&gt;
&lt;p&gt;Select it only when a documented integration requires it and the Cognito app client explicitly enables &lt;code&gt;ALLOW_USER_PASSWORD_AUTH&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;signIn&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;username&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;authFlowType&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;USER_PASSWORD_AUTH&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Tradeoff: this flow is simple and compatible with some migrations, but SRP avoids sending the password itself to Cognito. &lt;code&gt;CUSTOM_WITHOUT_SRP&lt;/code&gt; is for a configured custom authentication challenge, not a generic fallback. Hosted UI/managed login with authorization code and PKCE can reduce the amount of authentication UI the app owns and is often preferable for federation.&lt;/p&gt;
&lt;h2 id="a-diagnostic-sequence"&gt;
&lt;a href="#a-diagnostic-sequence" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A diagnostic sequence
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Confirm the app points to the expected Region, user pool, and app client.&lt;/li&gt;
&lt;li&gt;Inspect the app client&amp;rsquo;s allowed explicit auth flows; do not enable several flows merely to hide the error.&lt;/li&gt;
&lt;li&gt;Check whether the user is confirmed, disabled, forced to reset a password, or subject to MFA.&lt;/li&gt;
&lt;li&gt;Confirm the username alias used by the pool: username, email, or phone.&lt;/li&gt;
&lt;li&gt;Verify network reachability and device clock, then collect a sanitized Amplify error name and Cognito request ID.&lt;/li&gt;
&lt;li&gt;Reproduce with a dedicated test user in a non-production pool whose settings match production.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Avoid converting every exception into “unknown.” Map known states to user-safe messages while preserving a non-sensitive diagnostic code for operators. Do not reveal whether an arbitrary email exists if that conflicts with the account-enumeration policy.&lt;/p&gt;
&lt;p&gt;Authentication tests need a cleanup strategy. Create purpose-specific users rather than sharing one account across parallel tests, keep MFA seeds and recovery channels in approved test-only storage, and delete or reset test identities predictably. Test against a pool configuration produced from the same infrastructure definition as production; a hand-edited test pool can make the application look correct while a required explicit auth flow or callback URL differs in production.&lt;/p&gt;
&lt;h2 id="migration-and-verification-checklist"&gt;
&lt;a href="#migration-and-verification-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration and verification checklist
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Upgrade to a supported Amplify v6 release and use modular &lt;code&gt;aws-amplify/auth&lt;/code&gt; imports.&lt;/li&gt;
&lt;li&gt;Remove app-client secrets from public applications.&lt;/li&gt;
&lt;li&gt;Remove forced &lt;code&gt;USER_PASSWORD_AUTH&lt;/code&gt; unless the flow is explicitly required and allowed.&lt;/li&gt;
&lt;li&gt;Implement sign-up confirmation, MFA, new-password, reset-password, and sign-out paths.&lt;/li&gt;
&lt;li&gt;Test invalid password, unknown user, disabled user, unconfirmed user, expired code, resend, MFA, and token refresh.&lt;/li&gt;
&lt;li&gt;Verify app restart/session restoration and global sign-out behavior.&lt;/li&gt;
&lt;li&gt;Redact logs and monitor Cognito/CloudWatch events under the approved privacy policy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Verified on &lt;strong&gt;2026-08-01&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="primary-sources"&gt;
&lt;a href="#primary-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Primary sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.amplify.aws/react-native/build-a-backend/auth/connect-your-frontend/sign-in/"&gt;Amplify sign-in documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow-methods.html"&gt;Cognito authentication flows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-settings-client-apps.html"&gt;Cognito app-client settings&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>A Safe Terraform Init, Plan, and Apply Workflow</title><link>https://www.yopa.page/blog/2024-12-24-terraform-init-vs-plan-vs-apply---a-complete-guide-with-examples.html</link><pubDate>Tue, 24 Dec 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-12-24-terraform-init-vs-plan-vs-apply---a-complete-guide-with-examples.html</guid><description>
&lt;p&gt;&lt;code&gt;terraform init&lt;/code&gt;, &lt;code&gt;plan&lt;/code&gt;, and &lt;code&gt;apply&lt;/code&gt; are not three maturity levels. They perform different parts of one workflow: initialize a working directory, calculate a proposed change, and execute an approved change.&lt;/p&gt;
&lt;h2 id="initialize-reproducibly"&gt;
&lt;a href="#initialize-reproducibly" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Initialize reproducibly
&lt;/h2&gt;
&lt;p&gt;Run &lt;code&gt;terraform init&lt;/code&gt; in a new checkout and after changing provider requirements, modules, or backend configuration. It installs providers/modules, prepares the backend, and writes dependency selections to &lt;code&gt;.terraform.lock.hcl&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;terraform init
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;terraform fmt -check -recursive
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;terraform validate
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Commit &lt;code&gt;.terraform.lock.hcl&lt;/code&gt; for a root configuration so CI and teammates use reviewed provider selections. Constrain Terraform and provider versions. Do not commit &lt;code&gt;.terraform/&lt;/code&gt;, state files, plan files, or credentials.&lt;/p&gt;
&lt;p&gt;Changing a backend can migrate state. Back up and inspect the current state location, authenticate to both backends, and review the migration prompt. &lt;code&gt;-reconfigure&lt;/code&gt; and &lt;code&gt;-migrate-state&lt;/code&gt; solve different problems; do not choose one blindly in automation.&lt;/p&gt;
&lt;h2 id="plan-against-the-right-state"&gt;
&lt;a href="#plan-against-the-right-state" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Plan against the right state
&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;terraform plan&lt;/code&gt; refreshes relevant remote objects and compares them with configuration and state. A clean plan means “no difference detected for this configuration, state, credentials, and refresh,” not that the infrastructure is secure or complete.&lt;/p&gt;
&lt;p&gt;For an approved deployment pipeline, save the reviewed plan:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;terraform plan -out&lt;span class="o"&gt;=&lt;/span&gt;release.tfplan
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;terraform show release.tfplan
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;terraform apply release.tfplan
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The saved plan can contain sensitive values and is tied to the configuration, state, provider selections, and environment used to create it. Treat it as a protected, short-lived artifact. Do not generate it on one untrusted machine and apply it much later elsewhere.&lt;/p&gt;
&lt;p&gt;Always inspect replacements and destroys, IAM broadening, network exposure, data migration, and cost-sensitive changes. A textual plan review is strongest when policy checks and tests enforce known invariants.&lt;/p&gt;
&lt;h2 id="state-and-environment-boundaries"&gt;
&lt;a href="#state-and-environment-boundaries" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
State and environment boundaries
&lt;/h2&gt;
&lt;p&gt;Use a remote backend with locking and encryption for team workflows, restrict state access, and version/backup it according to the backend. State often contains sensitive values even when configuration marks outputs sensitive.&lt;/p&gt;
&lt;p&gt;Terraform CLI workspaces are separate state instances in one working directory/backend. They are useful for temporary or closely related copies, but they are not strong isolation for production environments that need different credentials and access controls. Prefer separate root configurations/backends or appropriately isolated HCP Terraform workspaces for those boundaries, sharing reusable modules rather than one state for everything.&lt;/p&gt;
&lt;h2 id="-target-is-exceptional"&gt;
&lt;a href="#-target-is-exceptional" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;code&gt;-target&lt;/code&gt; is exceptional
&lt;/h2&gt;
&lt;p&gt;Resource targeting is for exceptional recovery or troubleshooting, not routine dependency ordering, partial environment deployment, or a faster test cycle. Terraform&amp;rsquo;s dependency graph already determines order. A targeted plan omits unrelated changes and can produce a misleading view of policy or cost.&lt;/p&gt;
&lt;p&gt;When an exceptional target is approved:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;terraform plan -target&lt;span class="o"&gt;=&lt;/span&gt;aws_instance.example
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Review and apply the exceptional operation.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;terraform plan
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Finish with a full plan and reconcile configuration/state. If teams repeatedly need targeting, split the configuration along lifecycle and ownership boundaries.&lt;/p&gt;
&lt;h2 id="apply-and-rollback"&gt;
&lt;a href="#apply-and-rollback" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Apply and rollback
&lt;/h2&gt;
&lt;p&gt;Without a saved plan, &lt;code&gt;terraform apply&lt;/code&gt; creates a new plan and asks for confirmation. &lt;code&gt;-auto-approve&lt;/code&gt; belongs only in controlled automation with preceding policy and approval gates. It is not a safe local shortcut for production.&lt;/p&gt;
&lt;p&gt;Terraform has no universal rollback command. Revert configuration and apply another reviewed plan when the provider supports reversal. Stateful replacements and schema changes may require backups, restore procedures, or application migration. Test those paths before deployment.&lt;/p&gt;
&lt;h2 id="verification-checklist"&gt;
&lt;a href="#verification-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Verification checklist
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Confirm binary/version, backend, workspace, cloud account, and caller identity.&lt;/li&gt;
&lt;li&gt;Run format check, validation, tests/policy checks, and a full plan.&lt;/li&gt;
&lt;li&gt;Review destroys, replacements, permissions, exposure, state moves, and unknown values.&lt;/li&gt;
&lt;li&gt;Apply exactly the saved plan in the same trusted pipeline.&lt;/li&gt;
&lt;li&gt;Run service-level smoke tests and observe alarms after apply.&lt;/li&gt;
&lt;li&gt;Run a final plan to detect unexpected drift.&lt;/li&gt;
&lt;li&gt;Store evidence and delete the sensitive plan artifact according to policy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Official documentation reviewed on &lt;strong&gt;2026-08-01&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/cli/commands/init"&gt;Terraform initialization&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/cli/commands/plan"&gt;Terraform planning&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/cli/commands/apply"&gt;Terraform apply&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/cli/workspaces"&gt;CLI workspace guidance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.hashicorp.com/terraform/enterprise/workspaces/run/modes-and-options"&gt;Resource targeting&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>DNS and Email Authentication Without the Misleading Shortcuts</title><link>https://www.yopa.page/blog/2024-11-25-understanding-dns-email-authentication-terms.html</link><pubDate>Mon, 25 Nov 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-11-25-understanding-dns-email-authentication-terms.html</guid><description>
&lt;p&gt;DNS publishes routing and policy data. It does not perform an HTTP redirect, prove that every sender is trustworthy, or guarantee email delivery. Understanding the boundary of each record prevents configurations that look correct but fail authentication or create outages.&lt;/p&gt;
&lt;h2 id="common-dns-records"&gt;
&lt;a href="#common-dns-records" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Common DNS records
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;A / AAAA:&lt;/strong&gt; map a hostname to an IPv4 or IPv6 address.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CNAME:&lt;/strong&gt; aliases one DNS name to another canonical name. It is not a browser redirect and normally cannot coexist with other data at the same owner name. DNS providers may offer ALIAS/ANAME-style flattening at the zone apex; that is provider behavior, not a CNAME at the apex.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MX:&lt;/strong&gt; identifies mail exchangers for a domain. A lower preference number is tried first. Multiple MX records help only when they lead to genuinely independent, correctly configured delivery paths; a fake “backup MX” can weaken filtering or queue mail incorrectly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TXT:&lt;/strong&gt; carries arbitrary text used by ownership checks and email policies. Publishing several unrelated TXT records is normal, but a domain must not publish multiple SPF records.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="spf-authorize-sending-infrastructure"&gt;
&lt;a href="#spf-authorize-sending-infrastructure" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
SPF: authorize sending infrastructure
&lt;/h2&gt;
&lt;p&gt;SPF evaluates whether the connecting mail server is authorized for the RFC5321.MailFrom/return-path domain. It does not by itself authenticate the visible &lt;code&gt;From&lt;/code&gt; header. Keep one SPF record per domain, end with an intentional qualifier, and watch the RFC-defined DNS lookup limit. Flattening provider ranges can become stale, while excessive &lt;code&gt;include&lt;/code&gt; chains can produce &lt;code&gt;permerror&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-dns" data-lang="dns"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="py"&gt;example.com. &lt;/span&gt;&lt;span class="k"&gt;TXT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;#34;v=spf1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;include:&lt;/span&gt;&lt;span class="nc"&gt;_spf.example-sender.com&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;-all&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Use a subdomain for a third-party sender when it reduces blast radius, and remove providers when they are no longer used.&lt;/p&gt;
&lt;h2 id="dkim-sign-messages"&gt;
&lt;a href="#dkim-sign-messages" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
DKIM: sign messages
&lt;/h2&gt;
&lt;p&gt;DKIM adds a cryptographic signature. The receiver retrieves the public key at a selector such as &lt;code&gt;selector1._domainkey.example.com&lt;/code&gt;. Protect the private key at the sender, use supported key sizes, rotate selectors, and keep an old selector published until messages signed with it have aged out.&lt;/p&gt;
&lt;p&gt;AWS SES Easy DKIM can generate and rotate keys, but the DNS records must remain correct. “Three CNAMEs” is an SES implementation detail, not the definition of DKIM.&lt;/p&gt;
&lt;h2 id="dmarc-require-alignment-and-publish-handling-policy"&gt;
&lt;a href="#dmarc-require-alignment-and-publish-handling-policy" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
DMARC: require alignment and publish handling policy
&lt;/h2&gt;
&lt;p&gt;DMARC passes when at least one of SPF or DKIM both passes and aligns with the visible &lt;code&gt;From&lt;/code&gt; domain. It then tells receivers the requested policy and where to send aggregate reports.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-dns" data-lang="dns"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="py"&gt;_dmarc.example.com. &lt;/span&gt;&lt;span class="k"&gt;TXT&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;#34;v=DMARC1&lt;/span&gt;&lt;span class="c"&gt;; p=none; rua=mailto:dmarc@example.com; adkim=r; aspf=r; pct=100&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Start with monitoring only after the mailbox and privacy handling for reports are ready. Inventory legitimate senders, fix alignment, then move deliberately to &lt;code&gt;quarantine&lt;/code&gt; and &lt;code&gt;reject&lt;/code&gt;. &lt;code&gt;p=none&lt;/code&gt; gathers data but does not ask receivers to block failing mail. Receiver behavior and local policy can still vary.&lt;/p&gt;
&lt;h2 id="safe-change-procedure"&gt;
&lt;a href="#safe-change-procedure" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Safe change procedure
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Inventory every service sending with the domain, including marketing, support, billing, alerts, and forwarding.&lt;/li&gt;
&lt;li&gt;Query current authoritative DNS and record TTLs.&lt;/li&gt;
&lt;li&gt;Configure DKIM and an aligned return-path for each sender.&lt;/li&gt;
&lt;li&gt;Publish one reviewed SPF policy and a DMARC monitoring record.&lt;/li&gt;
&lt;li&gt;Validate syntax with authoritative queries and send test messages to independent receivers.&lt;/li&gt;
&lt;li&gt;Inspect &lt;code&gt;Authentication-Results&lt;/code&gt; headers and aggregate reports.&lt;/li&gt;
&lt;li&gt;Tighten DMARC gradually, monitoring false positives and forwarded/list mail.&lt;/li&gt;
&lt;li&gt;Document owner, purpose, provider, selector, and removal date for every record.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;DNS caching means rollback is not instant. Lowering TTL only helps after the previous TTL has elapsed. DNSSEC protects authenticity of DNS responses when correctly deployed, but it does not replace SPF, DKIM, or DMARC.&lt;/p&gt;
&lt;p&gt;Official standards reviewed on &lt;strong&gt;2026-08-01&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc7208"&gt;SPF, RFC 7208&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc6376"&gt;DKIM, RFC 6376&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc7489"&gt;DMARC, RFC 7489&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/ses/latest/dg/send-email-authentication-dkim-easy.html"&gt;Amazon SES Easy DKIM&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Working with VS Code Multi-root Workspaces</title><link>https://www.yopa.page/blog/2024-11-05-boost-your-productivity-with-vs-code-multi-root-workspaces.html</link><pubDate>Tue, 05 Nov 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-11-05-boost-your-productivity-with-vs-code-multi-root-workspaces.html</guid><description>
&lt;blockquote&gt;
&lt;p&gt;Learn how to efficiently manage multiple project folders in Visual Studio Code using multi-root workspaces. Discover setup tips, best practices, and advanced configurations for improved development workflow and knowledge management.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://code.visualstudio.com/"&gt;Visual Studio Code&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As developers, juggling multiple projects or working with distributed codebases is a common challenge. Visual Studio Code&amp;rsquo;s multi-root workspaces feature offers an elegant solution to this problem, allowing you to manage multiple folders within a single editor window. Let&amp;rsquo;s dive into how this powerful feature can transform your development workflow.&lt;/p&gt;
&lt;h2 id="what-makes-multi-root-workspaces-special"&gt;
&lt;a href="#what-makes-multi-root-workspaces-special" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What Makes Multi-root Workspaces Special?
&lt;/h2&gt;
&lt;p&gt;Multi-root workspaces remain supported. Prefer relative folder paths in shared &lt;code&gt;.code-workspace&lt;/code&gt; files, keep machine-specific secrets out of workspace settings, and confirm that each extension supports folder-scoped configuration.&lt;/p&gt;
&lt;p&gt;Think of multi-root workspaces as your digital workspace organizer. Instead of cluttering your taskbar with multiple VS Code windows or constantly switching between different project folders, you can have everything you need in one place. This feature is particularly valuable for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Full-stack developers working with separate frontend and backend codebases&lt;/li&gt;
&lt;li&gt;Teams managing microservices architectures&lt;/li&gt;
&lt;li&gt;Developers maintaining documentation alongside their code&lt;/li&gt;
&lt;li&gt;Anyone working with related projects spread across different locations&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="unified-knowledge-folders"&gt;
&lt;a href="#unified-knowledge-folders" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Unified knowledge folders
&lt;/h2&gt;
&lt;h3 id="integrating-with-obsidian-and-icloud"&gt;
&lt;a href="#integrating-with-obsidian-and-icloud" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Integrating with Obsidian and iCloud
&lt;/h3&gt;
&lt;p&gt;One of the most powerful applications of multi-root workspaces is creating a unified knowledge management system that works seamlessly across all your devices. Here&amp;rsquo;s why this combination is particularly powerful:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Single Source of Truth:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keep your Obsidian vault in iCloud while maintaining direct access through VS Code&lt;/li&gt;
&lt;li&gt;Access the same knowledge base whether you&amp;rsquo;re on your work laptop, personal computer, or mobile device&lt;/li&gt;
&lt;li&gt;Ensure your documentation and notes are always in sync across all platforms&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enhanced GenAI Interactions:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Maintain a structured record of your progress and learning&lt;/li&gt;
&lt;li&gt;Use your knowledge base as context when interacting with AI tools&lt;/li&gt;
&lt;li&gt;Avoid repeating yourself in AI conversations by referencing your documented progress&lt;/li&gt;
&lt;li&gt;Create more meaningful and context-aware AI interactions&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Progressive Documentation:&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;folders&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;~/Library/Mobile Documents/iCloud~md~obsidian/Documents/MyVault&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Knowledge Base&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./current-project&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Active Project&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ol start="4"&gt;
&lt;li&gt;
&lt;p&gt;Workflow Integration:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Track project progress alongside your knowledge base&lt;/li&gt;
&lt;li&gt;Reference and update documentation in real-time as you code&lt;/li&gt;
&lt;li&gt;Maintain project-specific notes that sync across all devices&lt;/li&gt;
&lt;li&gt;Create living documentation that evolves with your understanding&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;AI-Assisted Knowledge Management:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use your documented progress to generate more relevant AI responses&lt;/li&gt;
&lt;li&gt;Maintain context across different AI conversations&lt;/li&gt;
&lt;li&gt;Build upon previous learnings and discoveries&lt;/li&gt;
&lt;li&gt;Create a feedback loop between your code, documentation, and AI interactions&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="setting-up-your-multi-root-workspace"&gt;
&lt;a href="#setting-up-your-multi-root-workspace" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Setting Up Your Multi-root Workspace
&lt;/h2&gt;
&lt;p&gt;There are two main approaches to creating a multi-root workspace:&lt;/p&gt;
&lt;h3 id="quick-setup-method"&gt;
&lt;a href="#quick-setup-method" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Quick Setup Method
&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Launch VS Code with your main project&lt;/li&gt;
&lt;li&gt;Navigate to &lt;code&gt;File &amp;gt; Add Folder to Workspace&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Select additional folders you want to include&lt;/li&gt;
&lt;li&gt;Save your workspace configuration&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="professional-setup-method"&gt;
&lt;a href="#professional-setup-method" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Professional Setup Method
&lt;/h3&gt;
&lt;p&gt;Create a &lt;code&gt;.code-workspace&lt;/code&gt; file with this structure:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;folders&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/path/to/your/primary/project&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/path/to/your/secondary/folder&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;settings&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="advanced-configuration-tips"&gt;
&lt;a href="#advanced-configuration-tips" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Advanced Configuration Tips
&lt;/h2&gt;
&lt;h3 id="1-portable-workspace-configuration"&gt;
&lt;a href="#1-portable-workspace-configuration" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1. Portable Workspace Configuration
&lt;/h3&gt;
&lt;p&gt;Make your workspace more shareable by using relative paths:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;folders&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Main Project&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;../docs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Documentation&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="2-folder-specific-settings"&gt;
&lt;a href="#2-folder-specific-settings" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2. Folder-Specific Settings
&lt;/h3&gt;
&lt;p&gt;Customize settings for different project folders:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;folders&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./client&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Frontend&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./server&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Backend&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;settings&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;./client&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;editor.tabSize&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;./server&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;editor.tabSize&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="3-knowledge-base-integration"&gt;
&lt;a href="#3-knowledge-base-integration" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
3. Knowledge Base Integration
&lt;/h3&gt;
&lt;p&gt;Configure your workspace to include your Obsidian vault:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;folders&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;~/Library/Mobile Documents/iCloud~md~obsidian/Documents/MyVault&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Knowledge Base&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./project1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Current Project&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;../reference-projects&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Reference Code&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;settings&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;files.exclude&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;**/.obsidian&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="best-practices-for-multi-root-workspaces"&gt;
&lt;a href="#best-practices-for-multi-root-workspaces" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Best Practices for Multi-root Workspaces
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Organize Thoughtfully&lt;/strong&gt;: Group related projects together for logical workflow&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Monitor Performance&lt;/strong&gt;: Keep the number of folders reasonable to maintain VS Code&amp;rsquo;s responsiveness&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use Clear Naming&lt;/strong&gt;: Give each folder a descriptive name in the workspace configuration&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Version Control&lt;/strong&gt;: Consider sharing your workspace configuration with team members&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Search Smartly&lt;/strong&gt;: Use the folder: filter in search to target specific projects&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="avoiding-common-pitfalls"&gt;
&lt;a href="#avoiding-common-pitfalls" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Avoiding Common Pitfalls
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Search Scope&lt;/strong&gt;: Be mindful that searches include all workspace folders by default&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extension Compatibility&lt;/strong&gt;: Verify that your essential extensions work well with multi-root workspaces&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resource Management&lt;/strong&gt;: Close unused folders to maintain optimal performance&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="best-practices-for-knowledge-driven-development"&gt;
&lt;a href="#best-practices-for-knowledge-driven-development" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Best Practices for Knowledge-Driven Development
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Consistent Documentation: Update your knowledge base as you code&lt;/li&gt;
&lt;li&gt;AI-Ready Notes: Structure your documentation to be easily referenced in AI conversations&lt;/li&gt;
&lt;li&gt;Progressive Refinement: Regularly review and update your notes based on new learnings&lt;/li&gt;
&lt;li&gt;Cross-Platform Sync: Ensure your workspace configuration works across different devices&lt;/li&gt;
&lt;li&gt;Context Preservation: Maintain links between related pieces of knowledge&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="real-world-applications"&gt;
&lt;a href="#real-world-applications" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Real-world Applications
&lt;/h2&gt;
&lt;p&gt;Multi-root workspaces excel in scenarios like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Managing frontend and backend codebases in full-stack applications&lt;/li&gt;
&lt;li&gt;Keeping documentation and code in sync&lt;/li&gt;
&lt;li&gt;Working with multiple microservices&lt;/li&gt;
&lt;li&gt;Maintaining configuration files alongside active projects&lt;/li&gt;
&lt;li&gt;Building a personal knowledge management system&lt;/li&gt;
&lt;li&gt;Creating AI-ready documentation&lt;/li&gt;
&lt;li&gt;Maintaining a learning journal alongside active projects&lt;/li&gt;
&lt;li&gt;Tracking project progress across multiple devices&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="looking-ahead"&gt;
&lt;a href="#looking-ahead" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Looking Ahead
&lt;/h2&gt;
&lt;p&gt;As modern development becomes increasingly complex, tools like VS Code&amp;rsquo;s multi-root workspaces become essential for maintaining productivity. This feature continues to evolve with each VS Code release, offering new capabilities for managing complex development environments.&lt;/p&gt;</description></item><item><title>Understanding Git Ignore Patterns - .gitignore vs .git/info/exclude</title><link>https://www.yopa.page/blog/2024-11-01-understanding-git-ignore-patterns-gitignore-vs-git-info-exclude.html</link><pubDate>Fri, 01 Nov 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-11-01-understanding-git-ignore-patterns-gitignore-vs-git-info-exclude.html</guid><description>
&lt;blockquote&gt;
&lt;p&gt;Learn when to use .git/info/exclude instead of .gitignore for managing ignored files in Git, with practical examples and use cases&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://git-scm.com/docs/gitignore"&gt;Git Documentation&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;When working with Git, we often need to specify files and directories that should be ignored by source control. While &lt;code&gt;.gitignore&lt;/code&gt; is the most commonly used method, Git provides another powerful but lesser-known option: &lt;code&gt;.git/info/exclude&lt;/code&gt;. Let&amp;rsquo;s explore both approaches and understand when to use each one.&lt;/p&gt;
&lt;h2 id="the-traditional-approach-gitignore"&gt;
&lt;a href="#the-traditional-approach-gitignore" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Traditional Approach: .gitignore
&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;.gitignore&lt;/code&gt; file is the standard way to specify which files Git should ignore. It&amp;rsquo;s:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Committed to the repository&lt;/li&gt;
&lt;li&gt;Shared with all collaborators&lt;/li&gt;
&lt;li&gt;Part of the project&amp;rsquo;s source control&lt;/li&gt;
&lt;li&gt;Ideal for project-wide ignore patterns&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-hidden-gem-gitinfoexclude"&gt;
&lt;a href="#the-hidden-gem-gitinfoexclude" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Hidden Gem: .git/info/exclude
&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;.git/info/exclude&lt;/code&gt; serves a similar purpose but with some key differences. This file:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Lives inside the &lt;code&gt;.git&lt;/code&gt; directory&lt;/li&gt;
&lt;li&gt;Is not committed to the repository&lt;/li&gt;
&lt;li&gt;Remains local to your machine&lt;/li&gt;
&lt;li&gt;Doesn&amp;rsquo;t affect other collaborators&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-to-use-gitinfoexclude"&gt;
&lt;a href="#when-to-use-gitinfoexclude" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
When to Use .git/info/exclude
&lt;/h2&gt;
&lt;p&gt;Here are several scenarios where using &lt;code&gt;.git/info/exclude&lt;/code&gt; makes more sense than &lt;code&gt;.gitignore&lt;/code&gt;:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Personal IDE Settings&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You use a different IDE than your team&lt;/li&gt;
&lt;li&gt;Example: Ignoring VSCode settings while others use IntelliJ&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;.vscode/
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Local Development Tools&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You have specific development tools that others don&amp;rsquo;t use&lt;/li&gt;
&lt;li&gt;Example: Local debugging tools or profilers&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;debug-tools/
my-profiler.log
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Machine-Specific Build Outputs&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Custom build directories that vary by developer&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;my-custom-build/
local-dist/
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Personal Test Files&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Test files you create for local experimentation&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;my-test-*.js
scratch/
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Temporary Working Files&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Files you generate during development but don&amp;rsquo;t want to share&lt;/li&gt;
&lt;/ul&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;temp-*.txt
working-draft/
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="real-world-example"&gt;
&lt;a href="#real-world-example" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Real-World Example
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s say you&amp;rsquo;re working on a Node.js project where:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The team uses &lt;code&gt;.gitignore&lt;/code&gt; for common patterns:
&lt;pre tabindex="0"&gt;&lt;code&gt;node_modules/
dist/
.env
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;But you personally:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Use VSCode for debugging&lt;/li&gt;
&lt;li&gt;Have a custom build script&lt;/li&gt;
&lt;li&gt;Keep local notes&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Instead of polluting the project&amp;rsquo;s &lt;code&gt;.gitignore&lt;/code&gt;, you can add to &lt;code&gt;.git/info/exclude&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# Personal IDE
.vscode/
launch.json
# Custom build output
my-build/
build-*.log
# Personal notes
dev-notes.md
TODO.txt
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="benefits-of-using-gitinfoexclude"&gt;
&lt;a href="#benefits-of-using-gitinfoexclude" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Benefits of Using .git/info/exclude
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Clean Repository&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keep the project&amp;rsquo;s &lt;code&gt;.gitignore&lt;/code&gt; focused on truly project-wide patterns&lt;/li&gt;
&lt;li&gt;Avoid cluttering with personal preferences&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;No Conflicts&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Your ignore patterns won&amp;rsquo;t conflict with other developers&amp;rsquo; workflows&lt;/li&gt;
&lt;li&gt;No need for team discussions about personal ignore patterns&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Flexibility&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Easily modify ignore patterns without creating commits&lt;/li&gt;
&lt;li&gt;Experiment with different tools without affecting the team&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Privacy&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Keep your working methods private&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t expose personal tools or workflows&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="best-practices"&gt;
&lt;a href="#best-practices" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Best Practices
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Use &lt;code&gt;.gitignore&lt;/code&gt; for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Project-wide ignore patterns&lt;/li&gt;
&lt;li&gt;Framework-specific files&lt;/li&gt;
&lt;li&gt;Common development artifacts&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use &lt;code&gt;.git/info/exclude&lt;/code&gt; for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Personal tooling&lt;/li&gt;
&lt;li&gt;Local development artifacts&lt;/li&gt;
&lt;li&gt;Machine-specific patterns&lt;/li&gt;
&lt;li&gt;Temporary working files&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="creating-and-editing-gitinfoexclude"&gt;
&lt;a href="#creating-and-editing-gitinfoexclude" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Creating and Editing .git/info/exclude
&lt;/h2&gt;
&lt;p&gt;Git reads &lt;code&gt;.git/info/exclude&lt;/code&gt; automatically. Do not point &lt;code&gt;core.excludesFile&lt;/code&gt; at it; that setting is for a separate user-level excludes file. Ignore rules never untrack a path that is already committed.&lt;/p&gt;
&lt;p&gt;To start using &lt;code&gt;.git/info/exclude&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Open the file in your default editor&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git config core.editor &lt;span class="s2"&gt;&amp;#34;code&amp;#34;&lt;/span&gt; &lt;span class="c1"&gt;# If using VSCode&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Or edit directly&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;vim .git/info/exclude
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Remember that the syntax is identical to &lt;code&gt;.gitignore&lt;/code&gt;, so all your existing knowledge transfers over.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;While &lt;code&gt;.gitignore&lt;/code&gt; remains the primary tool for specifying ignore patterns in Git, &lt;code&gt;.git/info/exclude&lt;/code&gt; provides a valuable complement for managing personal ignore patterns. By understanding and using both tools appropriately, you can maintain a cleaner repository while accommodating individual development workflows.&lt;/p&gt;</description></item><item><title>Git Branches, Remotes, and HEAD</title><link>https://www.yopa.page/blog/2024-10-15-master-git-understanding-branches-remotes-and-head-for-better-collaboration.html</link><pubDate>Tue, 15 Oct 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-10-15-master-git-understanding-branches-remotes-and-head-for-better-collaboration.html</guid><description>
&lt;blockquote&gt;
&lt;p&gt;Demystify key Git concepts like local vs. remote branches, fetching, pulling, pushing, and the HEAD pointer to improve your version control skills and team collaboration.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://git-scm.com/doc"&gt;Git Official Documentation&lt;/a&gt;&lt;/p&gt;
&lt;h1 id="demystifying-git-understanding-branches-remotes-and-head"&gt;
&lt;a href="#demystifying-git-understanding-branches-remotes-and-head" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Demystifying Git: Understanding Branches, Remotes, and HEAD
&lt;/h1&gt;
&lt;p&gt;As developers, we use Git and GitHub daily, but some concepts can still trip us up. Today, we&amp;rsquo;re going to demystify some common Git scenarios and concepts that often cause confusion. We&amp;rsquo;ll focus on the relationship between local and remote branches, the git fetch command, and the ever-mysterious HEAD.&lt;/p&gt;
&lt;h2 id="the-tale-of-two-branches-local-vs-remote"&gt;
&lt;a href="#the-tale-of-two-branches-local-vs-remote" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Tale of Two Branches: Local vs. Remote
&lt;/h2&gt;
&lt;p&gt;Imagine you&amp;rsquo;re working on a feature branch called &lt;code&gt;awesome-feature&lt;/code&gt;. You&amp;rsquo;ve created this branch locally and pushed it to GitHub. Now, both your local machine and GitHub have a branch called &lt;code&gt;awesome-feature&lt;/code&gt;. But here&amp;rsquo;s the kicker: these two branches can diverge!&lt;/p&gt;
&lt;h3 id="real-life-scenario"&gt;
&lt;a href="#real-life-scenario" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Real-life scenario:
&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;You create &lt;code&gt;awesome-feature&lt;/code&gt; locally and push it to GitHub.&lt;/li&gt;
&lt;li&gt;Your colleague, Alice, pulls &lt;code&gt;awesome-feature&lt;/code&gt;, makes some changes, and pushes to GitHub.&lt;/li&gt;
&lt;li&gt;Now, the &lt;code&gt;awesome-feature&lt;/code&gt; on GitHub has changes that your local &lt;code&gt;awesome-feature&lt;/code&gt; doesn&amp;rsquo;t.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This is where the concept of &amp;ldquo;remote branches&amp;rdquo; comes in. Your local Git keeps track of the state of branches on GitHub (or any remote) using &amp;ldquo;remote-tracking branches&amp;rdquo;. These are usually named &lt;code&gt;origin/branch-name&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="fetching-updating-your-local-knowledge"&gt;
&lt;a href="#fetching-updating-your-local-knowledge" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Fetching: Updating Your Local Knowledge
&lt;/h2&gt;
&lt;p&gt;When you run &lt;code&gt;git fetch origin&lt;/code&gt;, you&amp;rsquo;re essentially asking Git to update its knowledge about the state of the remote repository. It&amp;rsquo;s like checking for new emails without actually opening them.&lt;/p&gt;
&lt;h3 id="what-git-fetch-does"&gt;
&lt;a href="#what-git-fetch-does" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What &lt;code&gt;git fetch&lt;/code&gt; does:
&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;It communicates with GitHub to see what changes exist on the remote.&lt;/li&gt;
&lt;li&gt;It updates your local remote-tracking branches (like &lt;code&gt;origin/awesome-feature&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;It doesn&amp;rsquo;t change your working directory or local branches.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After fetching, you can see what&amp;rsquo;s new:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git fetch origin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git diff awesome-feature origin/awesome-feature
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This shows you what changes are on GitHub that you don&amp;rsquo;t have locally.&lt;/p&gt;
&lt;h2 id="pull-fetch--merge"&gt;
&lt;a href="#pull-fetch--merge" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pull: Fetch + Merge
&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;git pull&lt;/code&gt; command is actually a combination of two steps: fetch and merge. It&amp;rsquo;s like checking for new emails and automatically putting them in your inbox.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git pull origin awesome-feature
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This fetches the latest changes from the &lt;code&gt;awesome-feature&lt;/code&gt; branch on GitHub and merges them into your current local branch.&lt;/p&gt;
&lt;h2 id="push-sharing-your-changes"&gt;
&lt;a href="#push-sharing-your-changes" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Push: Sharing Your Changes
&lt;/h2&gt;
&lt;p&gt;When you try to push your changes, Git checks if your local branch has diverged from the remote branch. If it has, you&amp;rsquo;ll get an error like:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;! [rejected] awesome-feature -&amp;gt; awesome-feature (non-fast-forward)
error: failed to push some refs to &amp;#39;https://github.com/your-repo.git&amp;#39;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This is Git&amp;rsquo;s way of protecting you from accidentally overwriting changes on the remote.&lt;/p&gt;
&lt;h3 id="how-to-resolve"&gt;
&lt;a href="#how-to-resolve" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
How to resolve:
&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;First, pull the latest changes:
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git pull origin awesome-feature
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;Resolve any merge conflicts if they occur.&lt;/li&gt;
&lt;li&gt;Then push your changes:
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push origin awesome-feature
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="head-and-detached-head"&gt;
&lt;a href="#head-and-detached-head" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
HEAD and detached HEAD
&lt;/h2&gt;
&lt;p&gt;HEAD is Git&amp;rsquo;s way of knowing &amp;ldquo;where you are&amp;rdquo; in the history of your project. It&amp;rsquo;s like a bookmark in a book.&lt;/p&gt;
&lt;h3 id="normal-head"&gt;
&lt;a href="#normal-head" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Normal HEAD:
&lt;/h3&gt;
&lt;p&gt;Usually, HEAD points to the latest commit of your current branch. When you make a new commit, HEAD moves forward automatically.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;HEAD -&amp;gt; awesome-feature -&amp;gt; Latest Commit
&lt;/code&gt;&lt;/pre&gt;&lt;h3 id="detached-head"&gt;
&lt;a href="#detached-head" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Detached HEAD:
&lt;/h3&gt;
&lt;p&gt;Sometimes, you might checkout a specific commit or a remote branch directly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git switch --detach abc123 &lt;span class="c1"&gt;# Some commit hash&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# or&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git switch --detach origin/awesome-feature
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now you&amp;rsquo;re in a &amp;ldquo;detached HEAD&amp;rdquo; state. It&amp;rsquo;s like you&amp;rsquo;ve placed your bookmark on a specific page, rather than at the end of a chapter.&lt;/p&gt;
&lt;p&gt;In this state:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You can look around and make experimental changes.&lt;/li&gt;
&lt;li&gt;If you make commits, they won&amp;rsquo;t belong to any branch.&lt;/li&gt;
&lt;li&gt;To save your work, create a new branch:
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git switch -c experimental-idea
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="best-practices"&gt;
&lt;a href="#best-practices" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Best Practices
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Fetch Often&lt;/strong&gt;: Regularly run &lt;code&gt;git fetch&lt;/code&gt; to stay updated with remote changes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Integrate Deliberately&lt;/strong&gt;: Fetch, inspect the divergence, then use the repository&amp;rsquo;s merge or rebase policy. A blind pull can create an unintended merge.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Use Branches&lt;/strong&gt;: Create feature branches for new work to keep main/master clean.&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>Git Diff for Pull Request Analysis</title><link>https://www.yopa.page/blog/2024-09-30-mastering-git-diff-for-efficient-pull-request-analysis-a-developers-guide.html</link><pubDate>Mon, 30 Sep 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-09-30-mastering-git-diff-for-efficient-pull-request-analysis-a-developers-guide.html</guid><description>
&lt;blockquote&gt;
&lt;p&gt;Learn how to streamline your pull request analysis using advanced Git diff techniques. This guide covers basic and advanced methods to focus on relevant code changes and improve your review process.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://git-scm.com/doc"&gt;Git Documentation&lt;/a&gt;&lt;/p&gt;
&lt;h1 id="git-diff-for-pull-request-analysis"&gt;
&lt;a href="#git-diff-for-pull-request-analysis" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Git Diff for Pull Request Analysis
&lt;/h1&gt;
&lt;p&gt;As a developer, reviewing pull requests is a crucial part of the software development process. While platforms like GitHub and GitLab offer user-friendly interfaces for this task, sometimes we need a more tailored approach. This guide will show you how to leverage the power of &lt;code&gt;git diff&lt;/code&gt; to analyze pull requests effectively and efficiently.&lt;/p&gt;
&lt;h2 id="understanding-the-basics-of-git-diff"&gt;
&lt;a href="#understanding-the-basics-of-git-diff" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Understanding the Basics of Git Diff
&lt;/h2&gt;
&lt;p&gt;Before diving into advanced techniques, let&amp;rsquo;s cover the fundamental &lt;code&gt;git diff&lt;/code&gt; commands for pull request analysis:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Ensure you&amp;rsquo;re on the target branch (usually main):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git switch main
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Fetch the latest changes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git fetch origin
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;View changes between the current branch and the pull request branch:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git diff origin/main...origin/feature-branch
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;e.g. &lt;code&gt;origin/release&lt;/code&gt; is the target branch, and &lt;code&gt;story/DX-16597&lt;/code&gt; is the pull request branch.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git diff origin/release...origin/story/DX-16597
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For a concise summary of changes:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git diff --stat origin/main...origin/feature-branch
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;To see only changed file names:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git diff --name-status origin/main...origin/feature-branch
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These commands provide a solid foundation for pull request analysis, but they often include more information than necessary.&lt;/p&gt;
&lt;h2 id="advanced-git-diff-techniques-for-focused-analysis"&gt;
&lt;a href="#advanced-git-diff-techniques-for-focused-analysis" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Advanced Git Diff Techniques for Focused Analysis
&lt;/h2&gt;
&lt;p&gt;To overcome the limitations of standard &lt;code&gt;git diff&lt;/code&gt; output, which often includes irrelevant files like &lt;code&gt;CHANGELOG.md&lt;/code&gt; or &lt;code&gt;package-lock.json&lt;/code&gt;, we can use a more refined approach:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git fetch origin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git diff origin/main...origin/feature-branch &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --unified&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;:!CHANGELOG.md&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;:!package-lock.json&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;:!yarn.lock&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;:!*.lock&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s1"&gt;&amp;#39;:!*.md&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;gt; pr_changes.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This command:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Fetches the latest changes&lt;/li&gt;
&lt;li&gt;Compares the main branch to the feature branch&lt;/li&gt;
&lt;li&gt;Shows only changed lines without context&lt;/li&gt;
&lt;li&gt;Excludes specific files and file types&lt;/li&gt;
&lt;li&gt;Outputs the result to a file&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;Review note: path exclusions should be deliberate. Lockfiles, migrations, documentation, and generated metadata can be security- or behavior-relevant and should not be hidden by default. Unmatched exclude pathspecs do not require deleting the pattern in normal modern Git usage.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 id="customizing-your-git-diff-approach"&gt;
&lt;a href="#customizing-your-git-diff-approach" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Customizing Your Git Diff Approach
&lt;/h2&gt;
&lt;p&gt;Pathspecs make the command adaptable, but every exclusion narrows the review evidence. You can easily modify the exclusion patterns to suit your project&amp;rsquo;s needs. For example, to exclude all JSON files, add &lt;code&gt;:!*.json&lt;/code&gt; to the list.&lt;/p&gt;
&lt;h2 id="implementing-the-advanced-git-diff-technique"&gt;
&lt;a href="#implementing-the-advanced-git-diff-technique" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Implementing the Advanced Git Diff Technique
&lt;/h2&gt;
&lt;p&gt;After running the advanced command, you&amp;rsquo;ll have a &lt;code&gt;pr_changes.txt&lt;/code&gt; file containing a focused diff of your pull request. You can:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Review the changes directly in this file&lt;/li&gt;
&lt;li&gt;Share the file with team members for collaborative review&lt;/li&gt;
&lt;li&gt;Use the content with AI tools for automated analysis without context limit concerns&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For a quick overview, use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;head -n &lt;span class="m"&gt;50&lt;/span&gt; pr_changes.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This displays the first 50 lines of the diff, giving you a snapshot of the changes.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;Compare from the merge base, keep review-relevant files visible, and use the hosting platform&amp;rsquo;s PR diff as a second view before approval.&lt;/p&gt;</description></item><item><title>Useful VS Code Shortcuts for Everyday Editing</title><link>https://www.yopa.page/blog/2024-09-21-essential-vs-code-shortcuts-for-efficient-coding.html</link><pubDate>Sat, 21 Sep 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-09-21-essential-vs-code-shortcuts-for-efficient-coding.html</guid><description>
&lt;p&gt;This reference lists common bindings; verify each one in the Keyboard Shortcuts editor for your operating system and keymap.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://code.visualstudio.com/docs/getstarted/keybindings"&gt;Visual Studio Code Keyboard Shortcuts Reference&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As developers, we&amp;rsquo;re always obsessed with ways to streamline our workflow and boost productivity. Visual Studio Code (VS Code) offers numerous shortcuts to help us code more efficiently. In this article, we&amp;rsquo;ll explore some cool VS Code shortcuts that can significantly improve your coding experience.&lt;/p&gt;
&lt;h2 id="quick-reference-table"&gt;
&lt;a href="#quick-reference-table" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Quick Reference Table
&lt;/h2&gt;
&lt;p&gt;Bindings vary by operating system, keymap extension, and user configuration. Use VS Code&amp;rsquo;s Keyboard Shortcuts editor as the source of truth and inspect conflicts before changing a binding.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Mac&lt;/th&gt;
&lt;th&gt;Windows/Linux&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Jump to Matching Bracket&lt;/td&gt;
&lt;td&gt;Cmd + Shift + \&lt;/td&gt;
&lt;td&gt;Ctrl + Shift + \&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Scroll Without Moving Cursor&lt;/td&gt;
&lt;td&gt;Ctrl + Fn + Up/Down Arrow&lt;/td&gt;
&lt;td&gt;Ctrl + Up/Down Arrow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Show/Hide Terminal&lt;/td&gt;
&lt;td&gt;Cmd + J&lt;/td&gt;
&lt;td&gt;Ctrl + J&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Expand/Shrink Selection&lt;/td&gt;
&lt;td&gt;Ctrl + Shift + Left/Right Arrow&lt;/td&gt;
&lt;td&gt;Shift + Alt + Left/Right Arrow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Change Tab&lt;/td&gt;
&lt;td&gt;Cmd + Opt + Left/Right Arrow&lt;/td&gt;
&lt;td&gt;Ctrl + PgUp/PgDn&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Find Symbol&lt;/td&gt;
&lt;td&gt;Cmd + T&lt;/td&gt;
&lt;td&gt;Ctrl + T&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-Cursor (select next occurrence)&lt;/td&gt;
&lt;td&gt;Cmd + D&lt;/td&gt;
&lt;td&gt;Ctrl + D&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Multi-Cursor (add above/below)&lt;/td&gt;
&lt;td&gt;Cmd + Option + Up/Down Arrow&lt;/td&gt;
&lt;td&gt;Ctrl + Alt + Up/Down Arrow&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Quick File Navigation&lt;/td&gt;
&lt;td&gt;Cmd + P&lt;/td&gt;
&lt;td&gt;Ctrl + P&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Toggle Line Comment&lt;/td&gt;
&lt;td&gt;Cmd + /&lt;/td&gt;
&lt;td&gt;Ctrl + /&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Rename Symbol&lt;/td&gt;
&lt;td&gt;F2&lt;/td&gt;
&lt;td&gt;F2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Open Command Palette&lt;/td&gt;
&lt;td&gt;Cmd + Shift + P&lt;/td&gt;
&lt;td&gt;Ctrl + Shift + P&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="detailed-explanations"&gt;
&lt;a href="#detailed-explanations" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Detailed Explanations
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Jump to Matching Bracket&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mac: Cmd + Shift + \&lt;/li&gt;
&lt;li&gt;Windows/Linux: Ctrl + Shift + \&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Quickly navigate between opening and closing brackets in nested code structures.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Jump Multiple Lines Vertically&lt;/strong&gt; (Custom shortcut)&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Requires configuration in &lt;code&gt;keybindings.json&lt;/code&gt;:&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ctrl+up&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cursorMove&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;args&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;to&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;up&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;by&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;line&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;when&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;editorTextFocus&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ctrl+down&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;cursorMove&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;args&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;to&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;down&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;by&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;line&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;value&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;when&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;editorTextFocus&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Move the cursor 10 lines up or down at once for faster navigation in large files.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Scroll Without Moving the Cursor&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mac: Ctrl + Fn + Up Arrow / Down Arrow&lt;/li&gt;
&lt;li&gt;Windows/Linux: Ctrl + Up Arrow / Down Arrow&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Scroll through your code while keeping your cursor in the same position.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Show/Hide Terminal&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mac: Cmd + J&lt;/li&gt;
&lt;li&gt;Windows/Linux: Ctrl + J&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Quickly toggle the integrated terminal for running commands.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Expand/Shrink Selection&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mac: Ctrl + Shift + Left Arrow / Right Arrow&lt;/li&gt;
&lt;li&gt;Windows/Linux: Shift + Alt + Left Arrow / Right Arrow&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Expand or shrink your selection based on code structure.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Change Tab&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mac: Cmd + Opt + Left / Right Arrow&lt;/li&gt;
&lt;li&gt;Windows/Linux: Ctrl + PgUp / PgDn&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Quickly switch between open tabs in your editor.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Find Symbol&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mac: Cmd + T&lt;/li&gt;
&lt;li&gt;Windows/Linux: Ctrl + T&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Find classes, functions, or properties anywhere in your current editor session with fuzzy search.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Multi-Cursor Editing&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Select next occurrence:
&lt;ul&gt;
&lt;li&gt;Mac: Cmd + D&lt;/li&gt;
&lt;li&gt;Windows/Linux: Ctrl + D&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Add cursor above/below:
&lt;ul&gt;
&lt;li&gt;Mac: Cmd + Option + Up Arrow / Down Arrow&lt;/li&gt;
&lt;li&gt;Windows/Linux: Ctrl + Alt + Up Arrow / Down Arrow&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Edit multiple occurrences of text simultaneously.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Quick File Navigation&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mac: Cmd + P&lt;/li&gt;
&lt;li&gt;Windows/Linux: Ctrl + P&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Open files in your project by typing part of the file name.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Toggle Line Comment&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mac: Cmd + /&lt;/li&gt;
&lt;li&gt;Windows/Linux: Ctrl + /&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Quickly comment or uncomment lines of code.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Rename Symbol&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mac/Windows/Linux: F2&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Rename variables, functions, or classes across your entire project.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Open Command Palette&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Mac: Cmd + Shift + P&lt;/li&gt;
&lt;li&gt;Windows/Linux: Ctrl + Shift + P&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Access all of VS Code&amp;rsquo;s commands quickly.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Use the shortcuts that remove friction from repeated work. There is no benefit in memorizing bindings that do not fit the way you edit or that conflict with accessibility needs.&lt;/p&gt;</description></item><item><title>Package and Install a Python Command-Line Application</title><link>https://www.yopa.page/blog/2026-08-01-package-and-install-python-cli.html</link><pubDate>Sat, 14 Sep 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-package-and-install-python-cli.html</guid><description>
&lt;p&gt;A shebang, executable bit, and symlink can expose one local script, but they do not describe dependencies, create an isolated environment, support Windows, or provide a reliable upgrade and uninstall path. A maintained CLI should be packaged as a Python application and installed in an isolated environment.&lt;/p&gt;
&lt;p&gt;This guide was verified on 2026-08-01 against the PyPA guides for &lt;a href="https://packaging.python.org/en/latest/guides/creating-command-line-tools/"&gt;creating command-line tools&lt;/a&gt; and &lt;a href="https://packaging.python.org/en/latest/guides/installing-stand-alone-command-line-tools/"&gt;installing standalone command-line tools&lt;/a&gt;, plus the &lt;a href="https://packaging.python.org/en/latest/specifications/entry-points/"&gt;entry-points specification&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="project-layout"&gt;
&lt;a href="#project-layout" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Project layout
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;code-collector/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── pyproject.toml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── src/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── code_collector/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── __init__.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── cli.py
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── tests/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └── test_cli.py
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Keep command behavior in a callable function. Return an exit code instead of calling &lt;code&gt;sys.exit&lt;/code&gt; deep inside business logic.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# src/code_collector/cli.py&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;argparse&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;parser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;argparse&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ArgumentParser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;parser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parse_args&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="declare-the-command"&gt;
&lt;a href="#declare-the-command" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Declare the command
&lt;/h2&gt;
&lt;p&gt;One standards-based &lt;code&gt;pyproject.toml&lt;/code&gt; example using Hatchling is:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-toml" data-lang="toml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;build-system&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;requires&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;hatchling&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;build-backend&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;hatchling.build&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;code-collector&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;version&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;0.1.0&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;requires-python&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;gt;=3.11&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;dependencies&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;project&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scripts&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;code-collector&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;code_collector.cli:main&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;[project.scripts]&lt;/code&gt; entry creates a platform-appropriate launcher. The same package can work on macOS, Linux, and Windows without asking users to create symlinks manually. Select and pin a maintained build backend according to repository policy; Hatchling is an example, not a universal requirement.&lt;/p&gt;
&lt;h2 id="develop-and-test-safely"&gt;
&lt;a href="#develop-and-test-safely" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Develop and test safely
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 -m venv .venv
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m pip install --editable .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;code-collector --help
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m pytest
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m build
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;An editable install is useful during development because source changes are visible without reinstalling. Build both wheel and source distribution before release, inspect their contents, and test the wheel in a fresh environment. Avoid importing or executing project code from build configuration.&lt;/p&gt;
&lt;h2 id="install-as-an-application"&gt;
&lt;a href="#install-as-an-application" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Install as an application
&lt;/h2&gt;
&lt;p&gt;For a local or published CLI, &lt;code&gt;pipx&lt;/code&gt; gives each application its own virtual environment while exposing its commands on &lt;code&gt;PATH&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pipx install .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;code-collector --help
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pipx upgrade code-collector
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pipx uninstall code-collector
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Users can install from an approved package index or an explicitly trusted source. Do not encourage installation from an unreviewed URL, and never embed index credentials in documentation or repository files.&lt;/p&gt;
&lt;h2 id="alternatives-and-tradeoffs"&gt;
&lt;a href="#alternatives-and-tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Alternatives and tradeoffs
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;A shell alias is adequate for a personal one-line convenience command, but is not a distributable application.&lt;/li&gt;
&lt;li&gt;A repository-local script can be run as &lt;code&gt;python path/to/script.py&lt;/code&gt;; this is simple when only contributors use it.&lt;/li&gt;
&lt;li&gt;A zipapp can distribute some pure-Python applications as one archive, but dependency and native-extension constraints require care.&lt;/li&gt;
&lt;li&gt;Tools such as Typer or Click provide richer CLI ergonomics; &lt;code&gt;argparse&lt;/code&gt; avoids an external runtime dependency.&lt;/li&gt;
&lt;li&gt;Native launchers or standalone bundlers can remove the requirement for a user-managed Python, at the cost of larger artifacts and platform-specific builds.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Identify the current command name, arguments, exit codes, configuration, and dependencies.&lt;/li&gt;
&lt;li&gt;Move behavior into an importable package and a small &lt;code&gt;main&lt;/code&gt; function.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;pyproject.toml&lt;/code&gt; metadata and a console entry point.&lt;/li&gt;
&lt;li&gt;Add tests for help, invalid input, normal output, and nonzero failures.&lt;/li&gt;
&lt;li&gt;Install the wheel in a clean environment on every supported platform.&lt;/li&gt;
&lt;li&gt;Document &lt;code&gt;pipx&lt;/code&gt; install, upgrade, and uninstall commands.&lt;/li&gt;
&lt;li&gt;Remove the old symlink only after &lt;code&gt;command -v code-collector&lt;/code&gt; resolves to the packaged launcher and acceptance tests pass.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="verification-gate"&gt;
&lt;a href="#verification-gate" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Verification gate
&lt;/h2&gt;
&lt;p&gt;Record the verification date and run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m build
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m pip install --force-reinstall dist/*.whl
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;code-collector --help
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m pytest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Also verify signal handling, filesystem permissions, encoding, and exit status for the environments the CLI actually supports.&lt;/p&gt;
&lt;h2 id="release-and-maintenance"&gt;
&lt;a href="#release-and-maintenance" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Release and maintenance
&lt;/h2&gt;
&lt;p&gt;Build releases from a tagged, clean commit and publish immutable versioned artifacts. When publishing to PyPI from CI, use trusted publishing where available instead of storing a long-lived token. Generate provenance evidence when the distribution context requires it.&lt;/p&gt;
&lt;p&gt;Treat stdout and stderr as interfaces. Keep machine-readable output stable or versioned, send diagnostics to stderr, and document exit codes. Avoid update checks or telemetry that delay startup or violate user expectations. When dropping a Python version, moving configuration, or renaming an option, publish a migration note and test the old failure path so users receive an actionable message. Assign ownership for dependency updates, supported platforms, vulnerability response, and retirement of obsolete releases.&lt;/p&gt;</description></item><item><title>Python Package Updates - A Guide to Updating and Verifying Your Packages</title><link>https://www.yopa.page/blog/2024-08-22-mastering-python-package-management-guide-updating-verifying-packages.html</link><pubDate>Thu, 22 Aug 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-08-22-mastering-python-package-management-guide-updating-verifying-packages.html</guid><description>
&lt;blockquote&gt;
&lt;p&gt;Learn how to keep your Python packages up-to-date and verify their versions for secure and efficient development.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://pypi.org/"&gt;Python Package Index (PyPI)&lt;/a&gt;&lt;/p&gt;
&lt;h1 id="updating-and-verifying-python-packages"&gt;
&lt;a href="#updating-and-verifying-python-packages" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Updating and Verifying Python Packages
&lt;/h1&gt;
&lt;p&gt;In the fast-paced world of Python development, staying up-to-date with your packages is crucial for maintaining secure, efficient, and bug-free applications. This guide will walk you through the essential steps of updating your Python packages and verifying their versions, ensuring you&amp;rsquo;re always working with the latest and greatest tools.&lt;/p&gt;
&lt;h2 id="updating-python-packages"&gt;
&lt;a href="#updating-python-packages" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Updating Python Packages
&lt;/h2&gt;
&lt;p&gt;Work in an isolated environment and update the project declaration or lock workflow, not merely the installed environment. Review release notes, resolve a small scope, and run the full test and build gates before merging.&lt;/p&gt;
&lt;p&gt;Once you&amp;rsquo;ve installed a Python package using pip, you might wonder how to keep it current. The process is straightforward:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Open your terminal or command prompt.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Use the following command to update a package:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;pip install --upgrade package_name
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Replace &lt;code&gt;package_name&lt;/code&gt; with the name of the package you want to update.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For example, to update a package called &amp;ldquo;requests&amp;rdquo;, you would run:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;python -m pip install --upgrade requests
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This command fetches the latest version of the package from the Python Package Index (PyPI) and installs it, replacing the older version.&lt;/p&gt;
&lt;h2 id="verifying-package-versions"&gt;
&lt;a href="#verifying-package-versions" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Verifying Package Versions
&lt;/h2&gt;
&lt;p&gt;After updating, it&amp;rsquo;s a good practice to verify that you&amp;rsquo;re running the latest version. Here&amp;rsquo;s how you can do that:&lt;/p&gt;
&lt;h3 id="1-check-the-installed-version"&gt;
&lt;a href="#1-check-the-installed-version" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1. Check the Installed Version
&lt;/h3&gt;
&lt;p&gt;To see the currently installed version of a package, use the &lt;code&gt;pip show&lt;/code&gt; command:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;python -m pip show package_name
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This displays information about the package, including its version number.&lt;/p&gt;
&lt;h3 id="2-check-the-latest-available-version"&gt;
&lt;a href="#2-check-the-latest-available-version" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2. Check the Latest Available Version
&lt;/h3&gt;
&lt;p&gt;To see all available versions of a package, including the latest, use:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;python -m pip index versions package_name
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The output will show all available versions, with the most recent typically at the top.&lt;/p&gt;
&lt;h3 id="3-compare-versions"&gt;
&lt;a href="#3-compare-versions" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
3. Compare Versions
&lt;/h3&gt;
&lt;p&gt;Compare the installed version (from step 1) with the latest available version (from step 2) to ensure you&amp;rsquo;re up-to-date.&lt;/p&gt;
&lt;h3 id="4-automated-check-for-outdated-packages"&gt;
&lt;a href="#4-automated-check-for-outdated-packages" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
4. Automated Check for Outdated Packages
&lt;/h3&gt;
&lt;p&gt;For a quick overview of all installed packages that have updates available, use:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;python -m pip list --outdated
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This command displays a list of packages with newer versions available, showing both the current and latest versions.&lt;/p&gt;
&lt;h2 id="best-practices"&gt;
&lt;a href="#best-practices" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Best Practices
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Regularly update your packages to benefit from bug fixes, security patches, and new features.&lt;/li&gt;
&lt;li&gt;Before updating packages in a production environment, test the updates in a development or staging environment to ensure compatibility.&lt;/li&gt;
&lt;li&gt;Use virtual environments to isolate project dependencies and avoid conflicts between different projects.
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.yopa.page/blog/2023-04-17-setting-up-virtual-environments-for-multiple-python-versions.html"&gt;Setting Up Virtual Environments for Multiple Python Versions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Keep an eye on the changelogs or release notes of critical packages to understand what&amp;rsquo;s new in each update.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>How to Ignore Files Locally in Git Without Modifying .gitignore</title><link>https://www.yopa.page/blog/2024-08-18-how-to-ignore-files-locally-in-git-without-modifying-gitignore.html</link><pubDate>Sun, 18 Aug 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-08-18-how-to-ignore-files-locally-in-git-without-modifying-gitignore.html</guid><description>
&lt;blockquote&gt;
&lt;p&gt;Learn how to use .git/info/exclude to ignore files locally in Git without affecting the shared .gitignore file&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://git-scm.com/docs"&gt;Git Documentation&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Have you ever needed to ignore certain files or directories in your Git project, but didn&amp;rsquo;t want to modify the shared .gitignore file? Perhaps you have some local development files or personal IDE settings that you don&amp;rsquo;t want to track, but also don&amp;rsquo;t want to burden your team with. There&amp;rsquo;s a simple solution for this: the .git/info/exclude file.&lt;/p&gt;
&lt;h2 id="using-gitinfoexclude"&gt;
&lt;a href="#using-gitinfoexclude" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Using .git/info/exclude
&lt;/h2&gt;
&lt;p&gt;The .git/info/exclude file works just like .gitignore, but it&amp;rsquo;s not tracked by Git. This means you can add custom ignore rules that apply only to your local repository without affecting other team members or the remote repository.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how to use it:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Navigate to your project&amp;rsquo;s root directory.&lt;/li&gt;
&lt;li&gt;Open the .git/info/exclude file in your favorite text editor. If it doesn&amp;rsquo;t exist, create it.&lt;/li&gt;
&lt;li&gt;Add your custom ignore rules to this file, one per line.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For example, you might add:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;# Ignore local development environment file
.env.local
# Ignore personal IDE settings
.vscode/
# Ignore a specific local directory
local_scripts/
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;These rules will apply only to your local repository and won&amp;rsquo;t be committed or pushed to the remote repository.&lt;/p&gt;
&lt;h2 id="benefits-of-using-gitinfoexclude"&gt;
&lt;a href="#benefits-of-using-gitinfoexclude" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Benefits of Using .git/info/exclude
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Keeps your personal ignore rules separate from the project&amp;rsquo;s shared rules.&lt;/li&gt;
&lt;li&gt;Avoids cluttering the shared .gitignore file with personal preferences.&lt;/li&gt;
&lt;li&gt;Prevents conflicts that might arise from different team members having different ignore needs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="when-to-use-gitignore-vs-gitinfoexclude"&gt;
&lt;a href="#when-to-use-gitignore-vs-gitinfoexclude" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
When to Use .gitignore vs .git/info/exclude
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Use .gitignore for ignore rules that should apply to all copies of the repository.&lt;/li&gt;
&lt;li&gt;Use .git/info/exclude for personal ignore rules that are specific to your local setup.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By leveraging .git/info/exclude, you can maintain a clean and personalized local Git environment without affecting your team&amp;rsquo;s workflow. It&amp;rsquo;s a simple yet powerful feature that every Git user should know about.&lt;/p&gt;</description></item><item><title>Evidence-Driven AI-Assisted Software Development</title><link>https://www.yopa.page/blog/2026-08-01-evidence-driven-ai-assisted-software-development.html</link><pubDate>Mon, 12 Aug 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-evidence-driven-ai-assisted-software-development.html</guid><description>
&lt;p&gt;Telling a model “act as a project manager, architect, then senior engineer” can improve presentation, but roles do not make its claims correct. Modern coding agents can inspect repositories, edit files, run commands, and use external tools. The useful unit of control is therefore not a persona. It is a bounded task with observable evidence and an approval boundary.&lt;/p&gt;
&lt;h2 id="the-current-mental-model"&gt;
&lt;a href="#the-current-mental-model" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The current mental model
&lt;/h2&gt;
&lt;p&gt;Treat the agent as an untrusted but capable collaborator operating inside a defined workspace. Give it the minimum context and tools needed for one outcome. Require it to distinguish repository facts, current external facts, assumptions, and user decisions. Let deterministic tools verify what they can; reserve human review for intent, architecture, security, and product judgment.&lt;/p&gt;
&lt;p&gt;A practical loop is:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Orient:&lt;/strong&gt; read repository instructions, current implementation, tests, ownership boundaries, and dirty-worktree state.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Specify:&lt;/strong&gt; state the goal, non-goals, acceptance tests, risk, data sensitivity, and rollback.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Plan:&lt;/strong&gt; identify interfaces and impacted behavior before editing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Change narrowly:&lt;/strong&gt; use small diffs with clear file ownership; preserve unrelated work.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Validate:&lt;/strong&gt; run formatting checks, type checks, unit/integration tests, builds, security checks, and targeted manual scenarios.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Review evidence:&lt;/strong&gt; inspect the diff and actual test output; challenge unsupported claims.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Release gradually:&lt;/strong&gt; use a branch, preview, feature flag, canary, or reversible migration proportional to risk.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Learn:&lt;/strong&gt; capture only durable project-specific gotchas, not raw private conversation.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="a-task-brief-that-is-more-useful-than-a-role-prompt"&gt;
&lt;a href="#a-task-brief-that-is-more-useful-than-a-role-prompt" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A task brief that is more useful than a role prompt
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Goal: Reject uploads larger than 10 MB before processing.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;In scope: API validation, error response, tests, docs.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Out of scope: Storage migration and UI redesign.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Constraints: Preserve current API shape; do not log filenames or content.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Acceptance:
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- 10 MB succeeds; 10 MB + 1 byte returns 413.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- Existing auth behavior is unchanged.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;- Unit and integration suites pass.
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Evidence required: changed-file list, test commands/results, remaining manual gate.
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The agent should first verify where upload limits are currently enforced. It should not invent a framework, environment, or production result. If a current library API or cloud feature matters, consult primary documentation and record the verification date.&lt;/p&gt;
&lt;h2 id="security-and-privacy-boundaries"&gt;
&lt;a href="#security-and-privacy-boundaries" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Security and privacy boundaries
&lt;/h2&gt;
&lt;p&gt;Repository text, issue content, retrieved web pages, and tool output can contain prompt injection. Treat them as data, not authority. Keep high-impact tools deny-by-default, review commands that publish, delete, deploy, or access secrets, and separate read-only discovery from mutation.&lt;/p&gt;
&lt;p&gt;Do not send customer data, credentials, private source, or meeting transcripts to a model unless the approved service and data policy permit it. Redact fixtures and logs. Ensure generated dependencies, licenses, network calls, and telemetry match organizational policy. An agent must never claim a production or device validation that only ran against a local fixture.&lt;/p&gt;
&lt;h2 id="evaluation-and-testing"&gt;
&lt;a href="#evaluation-and-testing" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Evaluation and testing
&lt;/h2&gt;
&lt;p&gt;Compile/build success is necessary but not sufficient. Use a layered gate:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;static checks for syntax, types, formatting, policy and secret scanning;&lt;/li&gt;
&lt;li&gt;deterministic tests for contracts and regression cases;&lt;/li&gt;
&lt;li&gt;integration tests at real boundaries using controlled environments;&lt;/li&gt;
&lt;li&gt;security tests for authorization, injection, data exposure, and failure handling;&lt;/li&gt;
&lt;li&gt;fresh-context human acceptance for usability and ambiguous requirements.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For repeated AI tasks, maintain an evaluation set with representative requests, expected properties, prohibited behavior, and cost/latency measurements. Do not optimize a prompt on one anecdote. Compare versions and preserve failures as regression cases where privacy permits.&lt;/p&gt;
&lt;h2 id="tradeoffs"&gt;
&lt;a href="#tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Tradeoffs
&lt;/h2&gt;
&lt;p&gt;Agents can accelerate discovery, repetitive edits, tests, and documentation, but larger autonomous changes increase review burden and correlated mistakes. Parallel agents help when file ownership and output contracts are disjoint; otherwise they create merge conflicts and inconsistent decisions. More context can improve local fit but also increases privacy exposure and distracts the model. The smallest sufficient context is usually better.&lt;/p&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Replace reusable persona prompts with task briefs and acceptance evidence.&lt;/li&gt;
&lt;li&gt;Add repository instructions for validation, secrets, ownership, and release.&lt;/li&gt;
&lt;li&gt;Inventory tool permissions and require approval for irreversible/external actions.&lt;/li&gt;
&lt;li&gt;Split large work into independently reviewable changes.&lt;/li&gt;
&lt;li&gt;Establish deterministic tests and a small evaluation suite before scaling usage.&lt;/li&gt;
&lt;li&gt;Record current-source citations for unstable technical claims.&lt;/li&gt;
&lt;li&gt;Require diff review, rollback notes, and an explicit list of unverified gates.&lt;/li&gt;
&lt;li&gt;Measure escaped defects and review time, not generated lines of code.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Verified on &lt;strong&gt;2026-08-01&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="primary-sources"&gt;
&lt;a href="#primary-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Primary sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.nist.gov/itl/ai-risk-management-framework"&gt;NIST AI Risk Management Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://csrc.nist.gov/Projects/ssdf"&gt;NIST Secure Software Development Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://genai.owasp.org/llm-top-10/"&gt;OWASP Top 10 for LLM Applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/copilot/responsible-use/copilot-coding-agent"&gt;GitHub guidance for responsible use of Copilot coding agent&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Efficiently Read a Git Repository from AWS Lambda</title><link>https://www.yopa.page/blog/2024-07-31-efficient-github-cloning-in-aws-lambda-sparse-checkout-to-the-rescue.html</link><pubDate>Wed, 31 Jul 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-07-31-efficient-github-cloning-in-aws-lambda-sparse-checkout-to-the-rescue.html</guid><description>
&lt;p&gt;AWS Lambda still has a 15-minute execution limit, but &lt;code&gt;/tmp&lt;/code&gt; is no longer fixed at 512 MB. Ephemeral storage is configurable from 512 MB to 10,240 MB. More space can make a job fit, but it does not fix unbounded clone time, unsafe credentials, concurrent capacity, or a workflow that needs to run longer than Lambda allows.&lt;/p&gt;
&lt;h2 id="pick-the-lightest-retrieval-method"&gt;
&lt;a href="#pick-the-lightest-retrieval-method" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pick the lightest retrieval method
&lt;/h2&gt;
&lt;p&gt;If you need files from one immutable commit or tag and do not need Git history, download a GitHub archive or use the Contents API. This avoids shipping a Git binary and object database in the Lambda package.&lt;/p&gt;
&lt;p&gt;If you need Git semantics, combine three distinct controls:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;shallow fetch&lt;/strong&gt; limits commit history;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;partial clone/filter&lt;/strong&gt; avoids downloading unneeded object contents when the server supports it;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;sparse checkout&lt;/strong&gt; limits which paths appear in the working tree.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Sparse checkout alone does not guarantee a small network transfer because objects outside the working tree may already have been fetched.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone --depth&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt; --filter&lt;span class="o"&gt;=&lt;/span&gt;blob:none --sparse &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --branch v1.2.3 https://github.com/example/project.git /tmp/project
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git -C /tmp/project sparse-checkout &lt;span class="nb"&gt;set&lt;/span&gt; --cone src config.json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git -C /tmp/project rev-parse --verify &lt;span class="s1"&gt;&amp;#39;HEAD^{commit}&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Treat a tag, branch, repository URL, and sparse path received from an event as untrusted. Prefer a full commit SHA from an allowlisted repository. Do not build a shell string from those values. In Python, call a subprocess with an argument array, set a timeout, capture bounded output, and clean a unique invocation directory in &lt;code&gt;finally&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="authentication"&gt;
&lt;a href="#authentication" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Authentication
&lt;/h2&gt;
&lt;p&gt;For a private GitHub repository, prefer a GitHub App installation token with narrow repository permissions and short lifetime. Store the app private key in an approved secret store and never put a token in the clone URL, where logs and errors can reveal it. Configure a credential helper or an HTTP authorization header without logging it. A personal access token tied to one developer has weaker ownership and rotation characteristics.&lt;/p&gt;
&lt;p&gt;Lambda&amp;rsquo;s AWS execution role and GitHub identity solve different boundaries. Scope the execution role to required secrets, S3, logs, or other AWS actions.&lt;/p&gt;
&lt;h2 id="storage-and-packaging"&gt;
&lt;a href="#storage-and-packaging" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Storage and packaging
&lt;/h2&gt;
&lt;p&gt;Set ephemeral storage from measured repository and concurrency needs, not the maximum by default. &lt;code&gt;/tmp&lt;/code&gt; is isolated per execution environment and can survive warm reuse, so use unique directories and never trust leftovers from a previous invocation. Encrypting temporary storage is handled by Lambda, but the data still belongs in the threat model and retention design.&lt;/p&gt;
&lt;p&gt;Git is not included in every Lambda runtime. Package a pinned Git build in a Lambda layer or container image and scan/update it. Verify architecture compatibility. Container image size and cold starts may make an archive/API approach simpler.&lt;/p&gt;
&lt;h2 id="when-lambda-is-the-wrong-runtime"&gt;
&lt;a href="#when-lambda-is-the-wrong-runtime" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
When Lambda is the wrong runtime
&lt;/h2&gt;
&lt;p&gt;Use Step Functions to coordinate bounded Lambda steps, AWS Batch or ECS/Fargate for long or resource-heavy repository analysis, or CodeBuild when the workload is fundamentally a build. Move away from Lambda when worst-case input approaches 15 minutes, needs large durable workspaces, or invokes many untrusted repository hooks/tools.&lt;/p&gt;
&lt;h2 id="verification-checklist"&gt;
&lt;a href="#verification-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Verification checklist
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Measure transfer, disk use, cold-start and runtime against the largest allowed repository.&lt;/li&gt;
&lt;li&gt;Test invalid and moved tags, annotated tags, missing sparse paths, timeout, rate limit, and partial-clone fallback.&lt;/li&gt;
&lt;li&gt;Verify commit SHA before analysis and reject unapproved repository hosts.&lt;/li&gt;
&lt;li&gt;Confirm tokens are absent from logs, exceptions, process arguments, and telemetry.&lt;/li&gt;
&lt;li&gt;Clean &lt;code&gt;/tmp&lt;/code&gt; and cap subprocess time/output.&lt;/li&gt;
&lt;li&gt;Test duplicate events and make downstream writes idempotent.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Official documentation reviewed on &lt;strong&gt;2026-08-01&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/configuration-ephemeral-storage.html"&gt;Lambda ephemeral storage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/gettingstarted-limits.html"&gt;Lambda quotas&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://git-scm.com/docs/partial-clone"&gt;Git partial clone&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://git-scm.com/docs/git-sparse-checkout"&gt;Git sparse checkout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/apps/creating-github-apps/authenticating-with-a-github-app/authenticating-as-a-github-app-installation"&gt;GitHub App installation authentication&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Resolving 'No Default Dev Hub' Error When Creating Salesforce Scratch Orgs</title><link>https://www.yopa.page/blog/2024-07-16-resolving-no-default-dev-hub-error-when-creating-salesforce-scratch-orgs.html</link><pubDate>Tue, 16 Jul 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-07-16-resolving-no-default-dev-hub-error-when-creating-salesforce-scratch-orgs.html</guid><description>
&lt;blockquote&gt;
&lt;p&gt;Learn how to fix the &amp;lsquo;No Default Dev Hub&amp;rsquo; error when creating Salesforce scratch orgs using Salesforce CLI, with step-by-step solutions and explanations.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_scratch_orgs.htm"&gt;Salesforce Developer Documentation&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;As a Salesforce developer, you&amp;rsquo;re likely familiar with the power and flexibility of scratch orgs for development and testing. However, you may occasionally encounter errors when trying to create these temporary environments. One common issue is the &amp;ldquo;No Default Dev Hub&amp;rdquo; error, which can be frustrating if you&amp;rsquo;re not sure how to resolve it. In this article, we&amp;rsquo;ll explore this error and provide two straightforward solutions to get you back on track.&lt;/p&gt;
&lt;h2 id="the-problem-no-default-dev-hub-error"&gt;
&lt;a href="#the-problem-no-default-dev-hub-error" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Problem: No Default Dev Hub Error
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s say you&amp;rsquo;re trying to create a scratch org using the Salesforce CLI with a command like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org create scratch --definition-file&lt;span class="o"&gt;=&lt;/span&gt;config/project-scratch-def.json --alias&lt;span class="o"&gt;=&lt;/span&gt;my-scratch --set-default --duration-days&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt; --wait&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;But instead of creating your scratch org, you&amp;rsquo;re met with this error:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;Error (NoDefaultDevHubError): No default dev hub found. Use -v or --target-dev-hub to specify an environment.
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This error occurs when the Salesforce CLI can&amp;rsquo;t find a default DevHub to use for creating your scratch org. Even if you have a DevHub set up and can see it when you run &lt;code&gt;sf org list&lt;/code&gt;, the CLI may not know which one to use by default.&lt;/p&gt;
&lt;h2 id="solution-1-set-a-default-devhub"&gt;
&lt;a href="#solution-1-set-a-default-devhub" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Solution 1: Set a Default DevHub
&lt;/h2&gt;
&lt;p&gt;The first and most straightforward solution is to set a default DevHub for your Salesforce CLI to use. Here&amp;rsquo;s how:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;First, identify your DevHub username. You can find this by running &lt;code&gt;sf org list&lt;/code&gt;. Look for the org with the &amp;ldquo;DevHub&amp;rdquo; type.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Once you have the username, set it as the default DevHub with this command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf config &lt;span class="nb"&gt;set&lt;/span&gt; target-dev-hub your-devhub-username@example.com
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Replace &lt;code&gt;your-devhub-username@example.com&lt;/code&gt; with your actual DevHub username.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;After setting the default, you should be able to run your original scratch org creation command without any issues.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This method is particularly useful if you primarily work with one DevHub, as you won&amp;rsquo;t need to specify it every time you create a scratch org.&lt;/p&gt;
&lt;h2 id="solution-2-specify-the-devhub-in-your-command"&gt;
&lt;a href="#solution-2-specify-the-devhub-in-your-command" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Solution 2: Specify the DevHub in Your Command
&lt;/h2&gt;
&lt;p&gt;If you work with multiple DevHubs or prefer not to set a default, you can explicitly specify which DevHub to use in your scratch org creation command. Here&amp;rsquo;s how:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Modify your original command to include the &lt;code&gt;-v&lt;/code&gt; or &lt;code&gt;--target-dev-hub&lt;/code&gt; flag, followed by your DevHub username:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org create scratch --definition-file&lt;span class="o"&gt;=&lt;/span&gt;config/project-scratch-def.json --alias&lt;span class="o"&gt;=&lt;/span&gt;my-scratch --set-default --duration-days&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt; --wait&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;15&lt;/span&gt; --target-dev-hub your-devhub-username@example.com
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Again, replace &lt;code&gt;your-devhub-username@example.com&lt;/code&gt; with your actual DevHub username.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run this modified command, and it should create your scratch org without the &amp;ldquo;No Default Dev Hub&amp;rdquo; error.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This method gives you more flexibility if you need to switch between different DevHubs for various projects or teams.&lt;/p&gt;
&lt;h2 id="verification-and-ci-guidance"&gt;
&lt;a href="#verification-and-ci-guidance" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Verification and CI guidance
&lt;/h2&gt;
&lt;p&gt;Run &lt;code&gt;sf org display --target-org my-scratch&lt;/code&gt; and confirm the org ID, expiration, and intended Dev Hub. Project-local &lt;code&gt;target-dev-hub&lt;/code&gt; is convenient for developers; an explicit &lt;code&gt;--target-dev-hub&lt;/code&gt; is safer in CI because it does not depend on ambient configuration. Verified against the &lt;a href="https://developer.salesforce.com/docs/platform/functions/guide/scratch-orgs.html"&gt;scratch org guide&lt;/a&gt; on 2026-08-01.&lt;/p&gt;
&lt;p&gt;The &amp;ldquo;No Default Dev Hub&amp;rdquo; error is a common hurdle in Salesforce development, but it&amp;rsquo;s easily overcome with these simple solutions. Whether you choose to set a default DevHub or specify it in each command, you&amp;rsquo;ll be able to create scratch orgs smoothly and continue your development work without interruption.&lt;/p&gt;
&lt;p&gt;Cheers! 🍺&lt;/p&gt;</description></item><item><title>Why `forEach` Cannot Handle Async - A Real-Life Example and Solutions</title><link>https://www.yopa.page/blog/2024-06-22-why-foreach-cannot-handle-async-a-real-life-example-and-solutions.html</link><pubDate>Sat, 22 Jun 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-06-22-why-foreach-cannot-handle-async-a-real-life-example-and-solutions.html</guid><description>
&lt;p&gt;In JavaScript, handling asynchronous operations can be tricky, especially when dealing with collections of data. One common pitfall is using the &lt;code&gt;forEach&lt;/code&gt; method for asynchronous tasks. This article explains why &lt;code&gt;forEach&lt;/code&gt; cannot handle async operations properly and presents solutions using &lt;code&gt;for...of&lt;/code&gt; loops or the &lt;code&gt;map&lt;/code&gt; method combined with &lt;code&gt;Promise.all&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="the-problem-with-foreach-and-async"&gt;
&lt;a href="#the-problem-with-foreach-and-async" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Problem with &lt;code&gt;forEach&lt;/code&gt; and Async
&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;forEach&lt;/code&gt; method is great for iterating over arrays, but it doesn&amp;rsquo;t handle asynchronous operations as you might expect. When you use &lt;code&gt;forEach&lt;/code&gt; with an async function, it does not wait for the promises to resolve before moving on to the next iteration. This can lead to unexpected behavior and incomplete operations.&lt;/p&gt;
&lt;h3 id="real-life-example-uploading-files"&gt;
&lt;a href="#real-life-example-uploading-files" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Real-Life Example: Uploading Files
&lt;/h3&gt;
&lt;p&gt;Imagine you are building a file upload feature that processes and uploads multiple files to a server. You might think using &lt;code&gt;forEach&lt;/code&gt; with an async function is a good approach. Let&amp;rsquo;s see what happens when you do this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;uploadFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePaths&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;filePaths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Assume readFile is a function that reads file content
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;uploadToServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Assume uploadToServer is a function that uploads the file content
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Uploaded &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;All files uploaded&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;file1.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;file2.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;file3.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;uploadFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You might expect to see log messages for each file upload followed by &amp;ldquo;All files uploaded&amp;rdquo;, but instead, you&amp;rsquo;ll likely see &amp;ldquo;All files uploaded&amp;rdquo; before any individual file upload messages. (surprise!) This happens because &lt;code&gt;forEach&lt;/code&gt; does not wait for the async operations to complete.&lt;/p&gt;
&lt;h2 id="solution-1-using-forof-loop"&gt;
&lt;a href="#solution-1-using-forof-loop" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Solution 1: Using &lt;code&gt;for...of&lt;/code&gt; Loop
&lt;/h2&gt;
&lt;p&gt;A more reliable way to handle asynchronous operations in a loop is using the &lt;code&gt;for...of&lt;/code&gt; statement. This approach ensures that each iteration waits for the async operations to finish before moving on to the next one.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;uploadFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePaths&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;filePaths&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Assume readFile is a function that reads file content
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;uploadToServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Assume uploadToServer is a function that uploads the file content
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Uploaded &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;All files uploaded&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;file1.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;file2.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;file3.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;uploadFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;With this approach, you will see each file upload log message in sequence, followed by &amp;ldquo;All files uploaded&amp;rdquo; at the end, as expected.&lt;/p&gt;
&lt;h2 id="solution-2-using-map-with-promiseall"&gt;
&lt;a href="#solution-2-using-map-with-promiseall" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Solution 2: Using &lt;code&gt;map&lt;/code&gt; with &lt;code&gt;Promise.all&lt;/code&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Promise.all&lt;/code&gt; is fail-fast and starts all mapped operations immediately. Use &lt;code&gt;Promise.allSettled&lt;/code&gt; when every result must be collected, or a maintained concurrency limiter when the input can exhaust sockets, memory, or service quotas.&lt;/p&gt;
&lt;p&gt;Another solution is to use the &lt;code&gt;map&lt;/code&gt; method to create an array of promises and then wait for all of them to resolve using &lt;code&gt;Promise.all&lt;/code&gt;. This approach is useful when you want to perform async operations in parallel.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;uploadFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePaths&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;uploadPromises&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;filePaths&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileContent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Assume readFile is a function that reads file content
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;uploadToServer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileContent&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Assume uploadToServer is a function that uploads the file content
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Uploaded &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;await&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;uploadPromises&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;All files uploaded&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;file1.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;file2.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;file3.txt&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;uploadFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This code will log the upload messages for each file, potentially out of order, but &amp;ldquo;All files uploaded&amp;rdquo; will be logged only after all files have been processed.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;Handling asynchronous operations in JavaScript requires careful consideration, especially when dealing with loops. The &lt;code&gt;forEach&lt;/code&gt; method is not suitable for async tasks because it does not wait for promises to resolve. Instead, use &lt;code&gt;for...of&lt;/code&gt; loops to handle async operations sequentially or &lt;code&gt;map&lt;/code&gt; with &lt;code&gt;Promise.all&lt;/code&gt; to handle them in parallel.&lt;/p&gt;
&lt;p&gt;Choose sequential, unbounded parallel, settled-result, or bounded-concurrency execution from the operation&amp;rsquo;s ordering, error, and resource requirements.&lt;/p&gt;</description></item><item><title>Salesforce CI/CD with AWS CodePipeline and CodeBuild</title><link>https://www.yopa.page/blog/2026-08-01-salesforce-ci-cd-with-aws-codepipeline-and-codebuild.html</link><pubDate>Sat, 15 Jun 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-salesforce-ci-cd-with-aws-codepipeline-and-codebuild.html</guid><description>
&lt;p&gt;Verified against AWS and Salesforce documentation on 2026-08-01.&lt;/p&gt;
&lt;h2 id="why-the-old-architecture-was-archived"&gt;
&lt;a href="#why-the-old-architecture-was-archived" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why the old architecture was archived
&lt;/h2&gt;
&lt;p&gt;AWS CodeDeploy deploys applications to AWS compute targets; it is not a Salesforce metadata deployment engine. A Salesforce source tree also doesn&amp;rsquo;t become a deployable “package” merely by converting it and putting the directory in S3. The archived pipeline mixed invalid CloudFormation, deprecated &lt;code&gt;sfdx&lt;/code&gt; commands, an unnecessary plugin install, and no credible authentication or rollback boundary.&lt;/p&gt;
&lt;p&gt;Use AWS only when the organization already operates CodePipeline/CodeBuild or needs its account controls. GitHub Actions, Salesforce DevOps Center, or another CI platform can be simpler. The important design is the release gate, not the vendor running the shell.&lt;/p&gt;
&lt;h2 id="supported-pipeline"&gt;
&lt;a href="#supported-pipeline" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Supported pipeline
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Source:&lt;/strong&gt; AWS CodeConnections delivers the reviewed Git revision to CodePipeline. Restrict the connection to the repository and branch required by the pipeline.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Validation:&lt;/strong&gt; CodeBuild uses a pinned image with the supported &lt;code&gt;sf&lt;/code&gt; CLI, authenticates without printing credentials, and runs tests plus &lt;code&gt;sf project deploy start --dry-run&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Evidence:&lt;/strong&gt; Store the commit, CLI version, target, component set, test results, and validation/deploy ID as build evidence. Do not store auth URLs, tokens, private keys, or customer data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Approval:&lt;/strong&gt; A production stage requires an explicit manual approval after validation. The approver checks the exact commit and target org.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Deploy:&lt;/strong&gt; A separate CodeBuild action deploys the same immutable revision. When Salesforce permits quick deploy for the validated result, use the validation/deploy ID; otherwise run the same scoped deployment again.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Verify:&lt;/strong&gt; Run smoke tests and check the deployment result and application behavior.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;CodeDeploy isn&amp;rsquo;t in this flow. Lambda is unnecessary unless orchestration requires a narrowly defined custom action that CodeBuild can&amp;rsquo;t perform.&lt;/p&gt;
&lt;h2 id="authentication-boundary"&gt;
&lt;a href="#authentication-boundary" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Authentication boundary
&lt;/h2&gt;
&lt;p&gt;CodeBuild&amp;rsquo;s AWS role should receive only the AWS permissions needed for source artifacts, logs, and a specific secret. Store Salesforce authentication material in AWS Secrets Manager and inject it only into the build that needs it. Prefer an approved workload/OIDC federation when the Salesforce and CI design supports it. Otherwise, use a dedicated least-privileged integration user and a supported noninteractive OAuth flow such as JWT bearer authentication, protecting and rotating the private key.&lt;/p&gt;
&lt;p&gt;Never bake a token or key into the image, repository, buildspec, environment-variable declaration, or artifact. Disable shell tracing around authentication. A Salesforce SFDX auth URL is a credential too.&lt;/p&gt;
&lt;h2 id="validation-build"&gt;
&lt;a href="#validation-build" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Validation build
&lt;/h2&gt;
&lt;p&gt;Pin the CLI version in the image or dependency lock and print only its version:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;0.2&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;phases&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;install&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;sf version&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;pre_build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;./scripts/authenticate-salesforce-from-secret&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;python3 scripts/validate_frontmatter.py&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;commands&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;sf project deploy preview --manifest manifest/package.xml --target-org prod&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;sf project deploy start --manifest manifest/package.xml --target-org prod --dry-run --test-level RunLocalTests --wait 60 --json &amp;gt; deploy-validation.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;artifacts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;deploy-validation.json&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The authentication helper must read the secret without echoing it, authorize the explicit alias, and remove temporary key material on exit. Add project-specific static analysis and unit tests before validation. Fail the build on any test, component, or JSON status failure; don&amp;rsquo;t rely only on the shell command producing a file.&lt;/p&gt;
&lt;h2 id="deployment-rollback-and-tradeoffs"&gt;
&lt;a href="#deployment-rollback-and-tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Deployment, rollback, and tradeoffs
&lt;/h2&gt;
&lt;p&gt;Quick deploy reduces repeated test time but is valid only within Salesforce&amp;rsquo;s permitted window and for the exact successful validation. Preserve its ID with the immutable commit. If it is unavailable, deploy the same manifest and commit normally.&lt;/p&gt;
&lt;p&gt;Metadata deployment is not a universal rollback system. Prepare a forward fix or a previously tested reverse deployment. Destructive changes, irreversible data migrations, package version promotion, and permission changes need separate gates and recovery plans. Keep data migration outside the metadata action and make it idempotent.&lt;/p&gt;
&lt;p&gt;CodePipeline provides AWS-native approvals and account audit trails, but CodeBuild startup and cross-cloud authentication add complexity. DevOps Center offers Salesforce-native workflows. GitHub Actions can reduce infrastructure when the source already lives on GitHub. Choose the platform the team can secure and operate; preserve the same validation, approval, identity, and rollback controls.&lt;/p&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Remove CodeDeploy, legacy &lt;code&gt;sfdx force:*&lt;/code&gt;, and generated S3 “package” assumptions.&lt;/li&gt;
&lt;li&gt;Establish CodeConnections with least repository access and an explicit trigger.&lt;/li&gt;
&lt;li&gt;Pin &lt;code&gt;sf&lt;/code&gt;, the build image, and project dependencies.&lt;/li&gt;
&lt;li&gt;Create a least-privileged Salesforce integration identity and Secrets Manager boundary.&lt;/li&gt;
&lt;li&gt;Separate validation and production deploy actions around manual approval.&lt;/li&gt;
&lt;li&gt;Prove the same commit, manifest, target org, and validation ID cross the gate.&lt;/li&gt;
&lt;li&gt;Test authentication failure, Apex failure, timeout, approval rejection, stale quick-deploy ID, and rollback.&lt;/li&gt;
&lt;li&gt;Add post-deploy smoke checks and alerts without exposing Salesforce payloads.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="official-sources"&gt;
&lt;a href="#official-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Official sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/codepipeline/latest/userguide/connections.html"&gt;AWS CodePipeline connections&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/codebuild/latest/userguide/build-spec-ref.html"&gt;AWS CodeBuild build specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/best-practices.html"&gt;AWS Secrets Manager best practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-reference/guide/cli_reference_project.html"&gt;Salesforce CLI deploy commands&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-plugin/guide/migrate-sfdx-sf.html"&gt;Salesforce CLI migration&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Use Salesforce Record Types Without Hard-Coded IDs</title><link>https://www.yopa.page/blog/2026-08-01-salesforce-record-types-without-hard-coded-ids.html</link><pubDate>Tue, 04 Jun 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-salesforce-record-types-without-hard-coded-ids.html</guid><description>
&lt;p&gt;Verified against Salesforce documentation on 2026-08-01.&lt;/p&gt;
&lt;h2 id="why-the-old-example-was-archived"&gt;
&lt;a href="#why-the-old-example-was-archived" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why the old example was archived
&lt;/h2&gt;
&lt;p&gt;Record Type IDs differ between orgs, so a map containing &lt;code&gt;012...&lt;/code&gt; values isn&amp;rsquo;t portable. The earlier Business Account versus Individual Account example also implied that an ordinary Account record type turns an Account into a person. Salesforce &lt;strong&gt;Person Accounts&lt;/strong&gt; are a distinct feature and data model combining Account and Contact behavior; enabling them is an organizational decision, not a record-type label.&lt;/p&gt;
&lt;h2 id="decide-whether-a-record-type-is-needed"&gt;
&lt;a href="#decide-whether-a-record-type-is-needed" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Decide whether a record type is needed
&lt;/h2&gt;
&lt;p&gt;Use a record type when one object supports materially different business processes, picklist value sets, or page-layout experiences. Don&amp;rsquo;t add one for a conditional field, security boundary, or reporting label. Dynamic Forms can handle presentation; permission sets handle access; a picklist may be enough for classification.&lt;/p&gt;
&lt;p&gt;For Accounts, decide first whether the business manages organizations only or requires Person Accounts. Confirm edition, feature implications, integrations, sharing, duplicate management, and reporting before enabling Person Accounts. Don&amp;rsquo;t simulate a person with a custom “Individual Account” record type unless that is an intentional custom model.&lt;/p&gt;
&lt;h2 id="resolve-ids-in-each-org"&gt;
&lt;a href="#resolve-ids-in-each-org" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Resolve IDs in each org
&lt;/h2&gt;
&lt;p&gt;In Apex, use describe metadata rather than SOQL or a hard-coded ID:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-apex" data-lang="apex"&gt;Schema.RecordTypeInfo info = Account.SObjectType
.getDescribe()
.getRecordTypeInfosByDeveloperName()
.get(&amp;#39;Business_Account&amp;#39;);
if (info == null || !info.isAvailable()) {
throw new IllegalArgumentException(&amp;#39;Business_Account is unavailable&amp;#39;);
}
Id businessRecordTypeId = info.getRecordTypeId();
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Check &lt;code&gt;isAvailable()&lt;/code&gt; because profile and permission assignments affect what the current user can use. In integration preprocessing, query by &lt;code&gt;SObjectType&lt;/code&gt; and &lt;code&gt;DeveloperName&lt;/code&gt; in the target org, cache only for that run, and fail if the result is absent or ambiguous:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf data query --target-org staging --use-tooling-api --query &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;SELECT Id, DeveloperName, Name, IsActive
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; FROM RecordType
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; WHERE SObjectType = &amp;#39;Account&amp;#39; AND DeveloperName = &amp;#39;Business_Account&amp;#39;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="safe-import-and-upsert"&gt;
&lt;a href="#safe-import-and-upsert" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Safe import and upsert
&lt;/h2&gt;
&lt;p&gt;CSV tools generally need the resolved &lt;code&gt;RecordTypeId&lt;/code&gt;, not a relationship column invented by the client. Keep &lt;code&gt;RecordTypeDeveloperName&lt;/code&gt; in the source export, resolve it against the target org, produce a temporary transformed CSV, and inspect unmapped values before loading.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;External_Id__c,Name,RecordTypeDeveloperName
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ORG-100,Acme Corp,Business_Account
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Load with a stable external ID only after transformation:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf data upsert bulk --target-org staging --sobject Account &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --file accounts-resolved.csv --external-id External_Id__c --wait &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Never copy RecordTypeIds or record IDs between environments. Protect input files containing customer data and reconcile successful, failed, and unprocessed results.&lt;/p&gt;
&lt;h2 id="tradeoffs-and-migration-checklist"&gt;
&lt;a href="#tradeoffs-and-migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Tradeoffs and migration checklist
&lt;/h2&gt;
&lt;p&gt;Record types give administrators a supported process boundary but multiply layouts, assignments, tests, and migration mappings. A single record type is simpler where behavior doesn&amp;rsquo;t truly diverge.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Inventory record types by object, developer name, purpose, assignments, and active use.&lt;/li&gt;
&lt;li&gt;Separate real business-process differences from UI, access, and reporting concerns.&lt;/li&gt;
&lt;li&gt;Decide explicitly whether Person Accounts are part of the model.&lt;/li&gt;
&lt;li&gt;Replace hard-coded IDs with describe or target-org lookup.&lt;/li&gt;
&lt;li&gt;Assign record types through profiles or permission sets and test least-privileged personas.&lt;/li&gt;
&lt;li&gt;Transform a small synthetic CSV, validate mapping counts, and upsert into a sandbox.&lt;/li&gt;
&lt;li&gt;Test create, edit, automation, integrations, duplicate rules, and reports for every supported type.&lt;/li&gt;
&lt;li&gt;Monitor failures after release and retain a rollback mapping.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="official-sources"&gt;
&lt;a href="#official-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Official sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.salesforce.com/docs/atlas.en-us.apexref.meta/apexref/apex_methods_system_sobject_describe.htm"&gt;Record Type information in Apex Schema&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://help.salesforce.com/s/articleView?id=sf.account_person.htm&amp;amp;type=5"&gt;Person Accounts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-reference/guide/cli_reference_data.html"&gt;Salesforce CLI data commands&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Choose the Right Salesforce API</title><link>https://www.yopa.page/blog/2026-08-01-choose-the-right-salesforce-api.html</link><pubDate>Sun, 05 May 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-choose-the-right-salesforce-api.html</guid><description>
&lt;p&gt;Verified on 2026-08-01.&lt;/p&gt;
&lt;h2 id="why-the-old-guide-changed"&gt;
&lt;a href="#why-the-old-guide-changed" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why the old guide changed
&lt;/h2&gt;
&lt;p&gt;Metadata, Tooling, and Bulk aren&amp;rsquo;t interchangeable sizes of one API. They address configuration, developer tooling, and high-volume data. A useful chooser must also include REST, SOAP, Composite, GraphQL, and UI API.&lt;/p&gt;
&lt;h2 id="decision-matrix"&gt;
&lt;a href="#decision-matrix" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Decision matrix
&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Need&lt;/th&gt;
&lt;th&gt;Start with&lt;/th&gt;
&lt;th&gt;Reason&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CRUD/query business records&lt;/td&gt;
&lt;td&gt;REST API&lt;/td&gt;
&lt;td&gt;Direct synchronous resource model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Several dependent REST operations&lt;/td&gt;
&lt;td&gt;Composite API&lt;/td&gt;
&lt;td&gt;Fewer round trips and optional transactional grouping&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Large asynchronous ingest/extract&lt;/td&gt;
&lt;td&gt;Bulk API 2.0&lt;/td&gt;
&lt;td&gt;Job-based CSV processing and partial-result handling&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Deploy/retrieve configuration&lt;/td&gt;
&lt;td&gt;Metadata API&lt;/td&gt;
&lt;td&gt;Metadata lifecycle and package manifests&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests, traces, coverage, dev artifacts&lt;/td&gt;
&lt;td&gt;Tooling API&lt;/td&gt;
&lt;td&gt;Developer-tool objects&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flexible graph-shaped reads&lt;/td&gt;
&lt;td&gt;GraphQL API&lt;/td&gt;
&lt;td&gt;Client-selected related fields&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Salesforce-shaped UI data&lt;/td&gt;
&lt;td&gt;UI API&lt;/td&gt;
&lt;td&gt;Layout, metadata, data, and user context&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Existing enterprise WSDL contract&lt;/td&gt;
&lt;td&gt;SOAP API&lt;/td&gt;
&lt;td&gt;Strong generated contract and mature integrations&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Choose first by resource: records, metadata, tooling, or UI. Then consider volume, latency, transaction boundaries, supported objects, API version, limits, and client capability.&lt;/p&gt;
&lt;h2 id="safe-examples"&gt;
&lt;a href="#safe-examples" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Safe examples
&lt;/h2&gt;
&lt;p&gt;For a small synchronous query, use REST through the authenticated CLI without exposing a token:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf data query --target-org integration-test &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --query &lt;span class="s2"&gt;&amp;#34;SELECT Id, Name FROM Account ORDER BY CreatedDate DESC LIMIT 10&amp;#34;&lt;/span&gt; --json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;For a large CSV upsert, use Bulk API 2.0 and reconcile every result category:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf data upsert bulk --target-org integration-test &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --sobject Account --file accounts.csv --external-id External_Id__c --wait &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Use OAuth with least scopes, an External Client App for new Salesforce OAuth integrations where applicable, TLS, secret rotation, timeouts, idempotency keys or stable external IDs, bounded retries, and structured logs without payloads or credentials.&lt;/p&gt;
&lt;h2 id="tradeoffs"&gt;
&lt;a href="#tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Tradeoffs
&lt;/h2&gt;
&lt;p&gt;REST is simple but chatty. Composite reduces calls but creates larger failure surfaces. GraphQL avoids over-fetching but isn&amp;rsquo;t a replacement for every mutation or metadata operation. UI API respects Salesforce UI semantics but couples clients to that model. Bulk API 2.0 scales but is asynchronous and can partially succeed. SOAP provides a rigid contract at the cost of XML and generated-client complexity.&lt;/p&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Inventory endpoints by resource, call volume, latency, and failure semantics.&lt;/li&gt;
&lt;li&gt;Remove claims that Bulk API is for reporting or Tooling API is a general refactoring engine.&lt;/li&gt;
&lt;li&gt;Move large record jobs to Bulk API 2.0 only when asynchronous partial results are acceptable.&lt;/li&gt;
&lt;li&gt;Keep configuration delivery on Metadata API and developer observations on documented Tooling objects.&lt;/li&gt;
&lt;li&gt;Pin an API version and test the next version before upgrading.&lt;/li&gt;
&lt;li&gt;Add limit, timeout, retry, duplicate, permission, and partial-failure tests.&lt;/li&gt;
&lt;li&gt;Verify record counts, metadata component sets, and final business state—not only HTTP success.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="official-sources"&gt;
&lt;a href="#official-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Official sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.salesforce.com/docs/apis"&gt;Salesforce Platform APIs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.salesforce.com/docs/atlas.en-us.api_rest.meta/api_rest/"&gt;REST API Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/bulk_api_2_0.htm"&gt;Bulk API 2.0 Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.salesforce.com/docs/platform/graphql/guide"&gt;GraphQL API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.salesforce.com/docs/platform/lwc/guide/reference-ui-api.html"&gt;UI API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Leveraging NPX and NVM for Efficient Backend Development</title><link>https://www.yopa.page/blog/2024-04-30-leveraging-npx-and-nvm-for-efficient-backend-development.html</link><pubDate>Tue, 30 Apr 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-04-30-leveraging-npx-and-nvm-for-efficient-backend-development.html</guid><description>
&lt;p&gt;NVM selects a Node.js runtime, while NPX or &lt;code&gt;npm exec&lt;/code&gt; runs a package command. They solve separate concerns and should be pinned according to the project policy.&lt;/p&gt;
&lt;h2 id="understanding-npx-and-nvm"&gt;
&lt;a href="#understanding-npx-and-nvm" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Understanding NPX and NVM
&lt;/h2&gt;
&lt;p&gt;Use a supported Node.js LTS pinned by the project. Prefer installed project scripts or &lt;code&gt;npm exec --package=name -- command&lt;/code&gt; with an explicit reviewed version; allowing NPX to download an unpinned package adds a supply-chain and reproducibility risk.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NPX&lt;/strong&gt; is a NPM tool that lets devs run Node packages without global installs. It&amp;rsquo;s handy for executing binaries from Node modules and running packages that leave no trace after execution.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;NVM&lt;/strong&gt;, on the other hand, is a version manager that lets you install multiple Node.js versions and switch between them effortlessly. It&amp;rsquo;s crucial for testing apps across Node versions or maintaining projects tied to specific Node versions.&lt;/p&gt;
&lt;h2 id="real-life-scenario-streamlined-project-setup"&gt;
&lt;a href="#real-life-scenario-streamlined-project-setup" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Real-Life Scenario: Streamlined Project Setup
&lt;/h2&gt;
&lt;p&gt;Imagine a backend dev juggling legacy Node.js versions and modern standards for a new microservice. Here&amp;rsquo;s how NPX and NVM can help:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Setting Up the Development Environment&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The developer can use &lt;code&gt;nvm&lt;/code&gt; to switch between Node versions as needed. For instance, starting with Node 12 for compatibility testing:
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nvm install &lt;span class="m"&gt;12&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nvm use &lt;span class="m"&gt;12&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;They then use &lt;code&gt;npx&lt;/code&gt; to run a scaffolding tool like &lt;code&gt;express-generator&lt;/code&gt; without installing it globally:
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npx express-generator my-new-service
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Development and Testing&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;As development progresses, testing on newer Node versions is necessary. Here, &lt;code&gt;nvm&lt;/code&gt; allows easy switching:
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nvm use &lt;span class="m"&gt;14&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;Using &lt;code&gt;npx&lt;/code&gt;, the developer can run local tests with tools like &lt;code&gt;mocha&lt;/code&gt; without permanent installations:
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npx mocha
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;CI/CD Integration&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In a continuous integration setup, ensuring that the microservice builds and runs tests across all supported Node versions can be scripted using both &lt;code&gt;nvm&lt;/code&gt; and &lt;code&gt;npx&lt;/code&gt;:
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nvm &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="m"&gt;12&lt;/span&gt; npx npm &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nvm &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="m"&gt;14&lt;/span&gt; npx npm &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This isolates microservices from version conflicts and global deps, ensuring each is tested and deployed with the right Node env.&lt;/p&gt;
&lt;h2 id="using-the-tools-together"&gt;
&lt;a href="#using-the-tools-together" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Using the tools together
&lt;/h2&gt;
&lt;p&gt;The combination of NPX and NVM can significantly reduce setup times, avoid conflicts between differing Node module versions, and facilitate a cleaner development environment. Backend developers benefit from:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Flexibility&lt;/strong&gt; in managing multiple Node.js environments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Convenience&lt;/strong&gt; of running any Node.js package executably without prior installation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Efficiency&lt;/strong&gt; in testing across different environments with minimal configuration changes.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Debug Flow-Invoked Queueable Apex with Correlated Logs</title><link>https://www.yopa.page/blog/2026-08-01-debug-flow-invoked-queueable-apex.html</link><pubDate>Sun, 28 Apr 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-debug-flow-invoked-queueable-apex.html</guid><description>
&lt;p&gt;This guide was verified against Salesforce documentation on 2026-08-01.&lt;/p&gt;
&lt;h2 id="why-the-old-debugging-flow-changed"&gt;
&lt;a href="#why-the-old-debugging-flow-changed" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why the old debugging flow changed
&lt;/h2&gt;
&lt;p&gt;The archived article started with a Flow invoking an &lt;code&gt;@future&lt;/code&gt; method. Salesforce now recommends Queueable Apex for production asynchronous work: it returns a job ID, accepts structured state, supports chaining, and is easier to monitor. A more important debugging detail was also missing. The Flow transaction and Queueable transaction are separate executions. Their logs can have different traced entities, timestamps, and governor-limit contexts.&lt;/p&gt;
&lt;p&gt;A useful trace must connect those transactions without logging an access token, Named Credential secret, customer payload, or full HTTP response.&lt;/p&gt;
&lt;h2 id="current-mental-model"&gt;
&lt;a href="#current-mental-model" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Current mental model
&lt;/h2&gt;
&lt;p&gt;Treat the path as three observable boundaries:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Flow interview&lt;/strong&gt; validates inputs and invokes a bulk-safe Apex action.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Invocable Apex&lt;/strong&gt; creates a correlation ID, enqueues one or more jobs within limits, and returns or persists the job ID.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Queueable Apex&lt;/strong&gt; runs later, under its own transaction and limits, and can be found through &lt;code&gt;AsyncApexJob&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The Flow debugger proves the synchronous path. Apex debug logs and &lt;code&gt;AsyncApexJob&lt;/code&gt; prove the asynchronous path. Neither alone proves the complete business outcome.&lt;/p&gt;
&lt;h2 id="a-correlation-friendly-implementation"&gt;
&lt;a href="#a-correlation-friendly-implementation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A correlation-friendly implementation
&lt;/h2&gt;
&lt;p&gt;Keep the invocable method bulk-safe. Don&amp;rsquo;t enqueue one job per record without considering the per-transaction queueable limit. The example groups the inputs into one job and generates a nonsecret correlation value.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-apex" data-lang="apex"&gt;public with sharing class SyncAccountsAction {
public class Request {
@InvocableVariable(required=true)
public Id accountId;
}
@InvocableMethod(label=&amp;#39;Queue Account Sync&amp;#39;)
public static void enqueue(List&amp;lt;Request&amp;gt; requests) {
Set&amp;lt;Id&amp;gt; accountIds = new Set&amp;lt;Id&amp;gt;();
for (Request request : requests) {
if (request != null &amp;amp;&amp;amp; request.accountId != null) {
accountIds.add(request.accountId);
}
}
if (accountIds.isEmpty()) return;
String correlationId = Crypto.getRandomUUID();
Id jobId = System.enqueueJob(
new SyncAccountsJob(new List&amp;lt;Id&amp;gt;(accountIds), correlationId)
);
System.debug(LoggingLevel.INFO,
&amp;#39;account-sync queued correlation=&amp;#39; + correlationId + &amp;#39; job=&amp;#39; + jobId);
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;pre tabindex="0"&gt;&lt;code class="language-apex" data-lang="apex"&gt;public with sharing class SyncAccountsJob
implements Queueable, Database.AllowsCallouts {
private final List&amp;lt;Id&amp;gt; accountIds;
private final String correlationId;
public SyncAccountsJob(List&amp;lt;Id&amp;gt; accountIds, String correlationId) {
this.accountIds = accountIds;
this.correlationId = correlationId;
}
public void execute(QueueableContext context) {
System.debug(LoggingLevel.INFO,
&amp;#39;account-sync started correlation=&amp;#39; + correlationId
+ &amp;#39; job=&amp;#39; + context.getJobId() + &amp;#39; count=&amp;#39; + accountIds.size());
// Query only required fields. Use a Named Credential for callouts.
// Log status, duration, and a provider request ID—not secrets or bodies.
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;If the correlation must survive log expiration, store a minimal status record containing the correlation ID, Queueable job ID, state, timestamps, and a sanitized error code. Apply retention and sharing rules; an observability object must not become a second store for customer payloads.&lt;/p&gt;
&lt;h2 id="set-the-right-trace-flags"&gt;
&lt;a href="#set-the-right-trace-flags" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Set the right trace flags
&lt;/h2&gt;
&lt;p&gt;Reproduce in a sandbox or scratch org with the smallest useful log window.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Trace the user who launches the Flow for the synchronous interview and invocable call.&lt;/li&gt;
&lt;li&gt;When automation runs as &lt;strong&gt;Automated Process&lt;/strong&gt;, add a trace flag for Automated Process as well.&lt;/li&gt;
&lt;li&gt;Keep Apex Code at a useful level such as &lt;code&gt;FINE&lt;/code&gt; or &lt;code&gt;FINEST&lt;/code&gt; only during the short diagnostic period. Raising every category to the maximum creates truncation and hides the useful sequence.&lt;/li&gt;
&lt;li&gt;If a scheduled or platform-event path starts the work, identify its actual execution user instead of assuming it matches the browser user.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Trigger one known record and note the start time and correlation ID. Then query the job:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf data query --target-org sandbox --use-tooling-api --query &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;SELECT Id, Status, JobType, ApexClass.Name, CreatedDate, CompletedDate,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; NumberOfErrors, ExtendedStatus
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; FROM AsyncApexJob
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; WHERE ApexClass.Name = &amp;#39;SyncAccountsJob&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; ORDER BY CreatedDate DESC LIMIT 10&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Use the returned job ID and timestamps to locate the Queueable log. A &lt;code&gt;Completed&lt;/code&gt; job means the Queueable transaction completed; it does not necessarily mean the remote system accepted or applied the business operation. Verify the external outcome through a safe provider request ID or a business-state query.&lt;/p&gt;
&lt;h2 id="secret-safe-logging"&gt;
&lt;a href="#secret-safe-logging" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Secret-safe logging
&lt;/h2&gt;
&lt;p&gt;Log identifiers that help correlation: job ID, correlation ID, record count, sanitized error category, HTTP status, duration, and an upstream request ID. Do not log:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;access or refresh tokens;&lt;/li&gt;
&lt;li&gt;Named Credential material or authorization headers;&lt;/li&gt;
&lt;li&gt;full request or response bodies;&lt;/li&gt;
&lt;li&gt;customer fields not required for diagnosis;&lt;/li&gt;
&lt;li&gt;stack traces in user-visible Flow errors.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Catch expected integration failures, persist a sanitized operational status, and let unexpected exceptions fail the Queueable so &lt;code&gt;AsyncApexJob&lt;/code&gt; records the failure. Avoid swallowing an exception after printing it.&lt;/p&gt;
&lt;h2 id="limits-and-tradeoffs"&gt;
&lt;a href="#limits-and-tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Limits and tradeoffs
&lt;/h2&gt;
&lt;p&gt;Queueable Apex is the default choice for single-shot asynchronous production work, but it still shares daily asynchronous capacity and has enqueue limits. Batch Apex is more appropriate for very large query-driven datasets. Platform Events can decouple producers and consumers, at the cost of event delivery and monitoring complexity. A synchronous invocable action is simplest when the work is quick and does not require a callout after DML.&lt;/p&gt;
&lt;p&gt;Chaining can express ordered stages, but every link needs an idempotency and stop strategy. Don&amp;rsquo;t build an unbounded retry loop. Use a bounded retry count, backoff appropriate to the external service, and a terminal failure state that an operator can inspect.&lt;/p&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Inventory each Flow that calls &lt;code&gt;@future&lt;/code&gt; and document its inputs and side effects.&lt;/li&gt;
&lt;li&gt;Create a bulk-safe invocable boundary and a Queueable class with focused tests.&lt;/li&gt;
&lt;li&gt;Add a correlation ID and retain the &lt;code&gt;System.enqueueJob&lt;/code&gt; job ID.&lt;/li&gt;
&lt;li&gt;Replace raw endpoints and secrets with a Named Credential.&lt;/li&gt;
&lt;li&gt;Define sanitized success, retryable failure, and terminal failure states.&lt;/li&gt;
&lt;li&gt;Test one record, a Flow-sized batch, callout failure, permission failure, and limit behavior.&lt;/li&gt;
&lt;li&gt;Deploy to a sandbox, add short-lived trace flags for the real execution identities, and compare business outcomes.&lt;/li&gt;
&lt;li&gt;Activate the revised Flow through a controlled release and remove obsolete trace flags.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="verification"&gt;
&lt;a href="#verification" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Verification
&lt;/h2&gt;
&lt;p&gt;A complete verification connects the Flow interview to the invocable log, the Queueable job ID, its asynchronous log, and the final business result. Tests should assert that inputs are bulked, the Queueable is enqueued, callouts are mocked, errors are classified without secrets, and retries terminate.&lt;/p&gt;
&lt;h2 id="official-sources"&gt;
&lt;a href="#official-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Official sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.salesforce.com/blogs/2023/05/leveling-up-your-apex-skills"&gt;Leveling Up Your Apex Skills: Queueable over future methods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_queueing_jobs.htm"&gt;Queueable Apex&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_annotation_InvocableMethod.htm"&gt;InvocableMethod Annotation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-reference/guide/cli_reference_data.html"&gt;Salesforce CLI data query reference&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>How to Fork a Repository, Make Changes, and Update a Submodule in Your Main Project</title><link>https://www.yopa.page/blog/2024-04-20-how-to-fork-a-repository-make-changes-and-update-a-submodule-in-your-main-project.html</link><pubDate>Sat, 20 Apr 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-04-20-how-to-fork-a-repository-make-changes-and-update-a-submodule-in-your-main-project.html</guid><description>
&lt;h3 id="how-to-fork-a-repository-make-changes-and-update-a-submodule-in-your-main-project"&gt;
&lt;a href="#how-to-fork-a-repository-make-changes-and-update-a-submodule-in-your-main-project" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
How to Fork a Repository, Make Changes, and Update a Submodule in Your Main Project
&lt;/h3&gt;
&lt;p&gt;Forking a repository on GitHub allows you to freely experiment with changes without affecting the original project. This is particularly useful in software development where you might want to customize a library, theme, or any other shared project to suit your own needs. In this blog, we will walk through the process of forking a repository, making updates, and then using those changes in a main project by updating a submodule.&lt;/p&gt;
&lt;h4 id="step-1-forking-a-repository"&gt;
&lt;a href="#step-1-forking-a-repository" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;Step 1: Forking a Repository&lt;/strong&gt;
&lt;/h4&gt;
&lt;p&gt;&lt;strong&gt;Forking&lt;/strong&gt; creates a copy of someone else&amp;rsquo;s repository under your GitHub account, allowing you to make changes without impacting the original repo. Here&amp;rsquo;s how to fork a repository:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Navigate to the Repository&lt;/strong&gt;: Go to the GitHub page of the repository you want to fork.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Fork the Repository&lt;/strong&gt;: Click the &amp;ldquo;Fork&amp;rdquo; button usually located at the top right of the page. This action will create a copy of the repository in your GitHub account.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="step-2-cloning-your-fork"&gt;
&lt;a href="#step-2-cloning-your-fork" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;Step 2: Cloning Your Fork&lt;/strong&gt;
&lt;/h4&gt;
&lt;p&gt;Once you have forked the repository, you will need to clone it to your local machine to make changes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Copy the Repository URL&lt;/strong&gt;: On your fork’s GitHub page, click the &amp;ldquo;Code&amp;rdquo; button and copy the URL provided.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Clone the Repository&lt;/strong&gt;: Open a terminal and run &lt;code&gt;git clone [URL]&lt;/code&gt;, replacing &lt;code&gt;[URL]&lt;/code&gt; with the repository URL you copied. This downloads the repository to your local machine.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="step-3-making-changes-in-your-fork"&gt;
&lt;a href="#step-3-making-changes-in-your-fork" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;Step 3: Making Changes in Your Fork&lt;/strong&gt;
&lt;/h4&gt;
&lt;p&gt;With the repository cloned, you can now navigate into the directory and start making changes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Navigate to the Repository Directory&lt;/strong&gt;: &lt;code&gt;cd repository-name&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create a New Branch&lt;/strong&gt; (optional but recommended): &lt;code&gt;git checkout -b your-new-branch-name&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make Your Changes&lt;/strong&gt;: Use your favorite code editor to make the changes you desire.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commit the Changes&lt;/strong&gt;: Use &lt;code&gt;git add .&lt;/code&gt; to stage changes, and &lt;code&gt;git commit -m &amp;quot;your commit message&amp;quot;&lt;/code&gt; to save them.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="step-4-pushing-changes-and-updating-your-fork"&gt;
&lt;a href="#step-4-pushing-changes-and-updating-your-fork" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;Step 4: Pushing Changes and Updating Your Fork&lt;/strong&gt;
&lt;/h4&gt;
&lt;p&gt;After committing your changes locally, you need to push them back to your GitHub fork:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Push the Changes&lt;/strong&gt;: &lt;code&gt;git push origin your-new-branch-name&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Create a Pull Request&lt;/strong&gt; (if you want to contribute back to the original repository): Go to your fork on GitHub, switch to your branch, and click &amp;ldquo;Pull request&amp;rdquo;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="step-5-updating-a-submodule-in-your-main-project"&gt;
&lt;a href="#step-5-updating-a-submodule-in-your-main-project" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;Step 5: Updating a Submodule in Your Main Project&lt;/strong&gt;
&lt;/h4&gt;
&lt;p&gt;If your fork is used as a submodule in another project, update your main project to use the latest changes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Navigate to the Submodule Directory&lt;/strong&gt;: &lt;code&gt;cd path-to-main-project/submodule-directory&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Pull the Latest Changes&lt;/strong&gt;: &lt;code&gt;git checkout main&lt;/code&gt; (or whichever branch you want), then &lt;code&gt;git pull origin main&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Commit the Submodule Changes in the Main Project&lt;/strong&gt;: Navigate back to the root of your main project, then run &lt;code&gt;git add submodule-directory&lt;/code&gt; and &lt;code&gt;git commit -m &amp;quot;Update submodule to latest commit&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Push the Updates&lt;/strong&gt;: &lt;code&gt;git push origin main&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="retargeting-a-submodule-to-a-fork"&gt;
&lt;a href="#retargeting-a-submodule-to-a-fork" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Retargeting a submodule to a fork
&lt;/h3&gt;
&lt;p&gt;Removing &lt;code&gt;.git/modules&lt;/code&gt; is unnecessary when only the remote URL changes. Use the supported URL workflow first:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git submodule set-url themes/hugo-tania https://github.com/yourusername/hugo-tania.git
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git submodule sync --recursive
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git submodule update --init --recursive
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add .gitmodules themes/hugo-tania
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;Point theme submodule to maintained fork&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The superproject records a specific submodule commit, not a floating branch. Review and commit that pointer deliberately.&lt;/p&gt;
&lt;h3 id="full-removal-as-a-fallback"&gt;
&lt;a href="#full-removal-as-a-fallback" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Full removal as a fallback
&lt;/h3&gt;
&lt;p&gt;Sometimes you might find it necessary to replace a submodule in your project with a fork that you have customized. This section will guide you through removing the existing submodule and adding your fork as the new submodule. In this example, we will assume that you have forked the &lt;a href="https://github.com/WingLim/hugo-tania"&gt;hugo-tania&lt;/a&gt; theme and want to use it in your project. (the blog you are reading is using this theme).&lt;/p&gt;
&lt;h4 id="step-1-navigate-to-your-main-project"&gt;
&lt;a href="#step-1-navigate-to-your-main-project" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;Step 1: Navigate to Your Main Project&lt;/strong&gt;
&lt;/h4&gt;
&lt;p&gt;Ensure you are in the root directory of your main project where the submodule is integrated:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; path/to/your/main/project
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h4 id="step-2-remove-the-existing-submodule-reference"&gt;
&lt;a href="#step-2-remove-the-existing-submodule-reference" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;Step 2: Remove the Existing Submodule Reference&lt;/strong&gt;
&lt;/h4&gt;
&lt;p&gt;To change the submodule reference, first, remove the existing submodule. This includes deleting the submodule&amp;rsquo;s entries from &lt;code&gt;.gitmodules&lt;/code&gt;, &lt;code&gt;.git/config&lt;/code&gt;, and the actual submodule directory:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git submodule deinit -f themes/hugo-tania
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;rm -rf .git/modules/themes/hugo-tania
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git rm -f themes/hugo-tania
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h4 id="step-3-commit-the-removal-of-the-submodule"&gt;
&lt;a href="#step-3-commit-the-removal-of-the-submodule" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;Step 3: Commit the Removal of the Submodule&lt;/strong&gt;
&lt;/h4&gt;
&lt;p&gt;Commit these changes to ensure your repository correctly reflects the removal of the submodule:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;Remove old submodule reference&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h4 id="step-4-add-your-fork-as-the-new-submodule"&gt;
&lt;a href="#step-4-add-your-fork-as-the-new-submodule" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;Step 4: Add Your Fork as the New Submodule&lt;/strong&gt;
&lt;/h4&gt;
&lt;p&gt;Now, add your fork as the new submodule. This step points the submodule reference to your forked repository:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git submodule add https://github.com/yourusername/hugo-tania.git themes/hugo-tania
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h4 id="step-5-initialize-and-update-the-submodule"&gt;
&lt;a href="#step-5-initialize-and-update-the-submodule" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;Step 5: Initialize and Update the Submodule&lt;/strong&gt;
&lt;/h4&gt;
&lt;p&gt;After adding the new submodule, initialize and update it to make sure it&amp;rsquo;s correctly set up:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git submodule update --init --recursive
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h4 id="step-6-commit-the-new-submodule-reference"&gt;
&lt;a href="#step-6-commit-the-new-submodule-reference" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;Step 6: Commit the New Submodule Reference&lt;/strong&gt;
&lt;/h4&gt;
&lt;p&gt;Once the submodule is added and updated, you need to commit these changes to your project’s repository:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add .gitmodules themes/hugo-tania
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;Update submodule to point to my fork&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h4 id="step-7-push-your-changes"&gt;
&lt;a href="#step-7-push-your-changes" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;Step 7: Push Your Changes&lt;/strong&gt;
&lt;/h4&gt;
&lt;p&gt;Finally, push the changes to your remote repository to ensure that everything is up-to-date:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push origin main
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This procedure ensures that your main project now uses the updated fork as a submodule, allowing you to benefit from your custom changes or enhancements. This approach is ideal for developers who need to modify dependencies while keeping track of their changes separately from the original submodule source.&lt;/p&gt;</description></item><item><title>Understanding the Git Remote Named 'origin'</title><link>https://www.yopa.page/blog/2024-04-05-understanding-origin-in-github.html</link><pubDate>Fri, 05 Apr 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-04-05-understanding-origin-in-github.html</guid><description>
&lt;p&gt;In the realm of Git and GitHub, grasping the concept of &lt;code&gt;origin&lt;/code&gt; is crucial for developers to effectively manage and collaborate on code. The term &lt;code&gt;origin&lt;/code&gt; might sound abstract, but it plays a critical role in your interactions with Git repositories. This article demystifies &lt;code&gt;origin&lt;/code&gt;, breaking down its importance and functionality in the context of GitHub.&lt;/p&gt;
&lt;h3 id="remote-repository-alias"&gt;
&lt;a href="#remote-repository-alias" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Remote Repository Alias
&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;origin&lt;/code&gt; is a conventional Git remote name created by &lt;code&gt;git clone&lt;/code&gt;, not a GitHub feature and not a guarantee. Inspect it with &lt;code&gt;git remote -v&lt;/code&gt;; repositories can rename it or have several remotes.&lt;/p&gt;
&lt;p&gt;When you clone a repository from GitHub, Git doesn&amp;rsquo;t leave you juggling with the full URL of your remote repository every time you need to interact with it. Instead, it kindly sets up a shorthand alias named &lt;code&gt;origin&lt;/code&gt;. This alias points directly to the URL of the cloned repository, simplifying your command-line operations.&lt;/p&gt;
&lt;p&gt;For instance, pushing changes to the &lt;code&gt;origin&lt;/code&gt; means you are updating the remote repository you originally cloned from:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push origin branch-name
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This command efficiently communicates your local changes back to the remote repository, tagged under the branch you specify.
&lt;strong&gt;push your local branch-name to origin(remote repo where you cloned your code from)&lt;/strong&gt;&lt;/p&gt;
&lt;h3 id="default-remote"&gt;
&lt;a href="#default-remote" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Default Remote
&lt;/h3&gt;
&lt;p&gt;The term &lt;code&gt;origin&lt;/code&gt; is not a whimsical choice but a standard convention in Git to refer to your primary remote repository. However, this doesn&amp;rsquo;t mean you are stuck with it. Git is flexible, allowing you to rename &lt;code&gt;origin&lt;/code&gt; to something more meaningful to you or even work with multiple remotes by giving each one a unique name.&lt;/p&gt;
&lt;h3 id="independence-from-the-default-branch"&gt;
&lt;a href="#independence-from-the-default-branch" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Independence from the Default Branch
&lt;/h3&gt;
&lt;p&gt;While &lt;code&gt;origin&lt;/code&gt; is a constant presence in your Git commands, it&amp;rsquo;s important to note that it does not tie specifically to any branch, such as the default &lt;code&gt;main&lt;/code&gt; or &lt;code&gt;master&lt;/code&gt; branch. Instead, &lt;code&gt;origin&lt;/code&gt; is your gateway to the entire remote repository, providing access to all its branches. For example, if you need to fetch updates from a &lt;code&gt;develop&lt;/code&gt; branch on the remote, you&amp;rsquo;d use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git fetch origin develop
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This command doesn&amp;rsquo;t care which branch is default; it fetches updates from &lt;code&gt;develop&lt;/code&gt;, demonstrating &lt;code&gt;origin&lt;/code&gt;&amp;rsquo;s versatility.&lt;/p&gt;
&lt;h3 id="wrapping-it-up"&gt;
&lt;a href="#wrapping-it-up" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Wrapping it up
&lt;/h3&gt;
&lt;p&gt;The concept of &lt;code&gt;origin&lt;/code&gt; in GitHub is a cornerstone for efficient repository management and collaboration. It is essentially a nickname or alias for the remote repository from which you cloned your code!&lt;/p&gt;</description></item><item><title>Setting Up a Shortcut for GitHub Copilot in VS Code</title><link>https://www.yopa.page/blog/2024-03-25-setting-up-a-shortcut-for-github-copilot-in-vs-code.html</link><pubDate>Mon, 25 Mar 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-03-25-setting-up-a-shortcut-for-github-copilot-in-vs-code.html</guid><description>
&lt;p&gt;GitHub Copilot has become an indispensable tool for many developers, offering AI-powered code suggestions that streamline the coding process. However, constantly navigating through menus to access Copilot can disrupt your workflow. This tutorial will guide you through setting up a keyboard shortcut in Visual Studio Code (VS Code) to quickly open the GitHub Copilot chat panel.&lt;/p&gt;
&lt;h3 id="step-by-step-guide-to-setting-up-your-shortcut"&gt;
&lt;a href="#step-by-step-guide-to-setting-up-your-shortcut" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step-by-Step Guide to Setting Up Your Shortcut
&lt;/h3&gt;
&lt;p&gt;Command IDs and default bindings can change with VS Code and the Copilot extension. Open Keyboard Shortcuts, search for the current Copilot Chat command by name, assign a non-conflicting key, and use the conflict view to verify context.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Open the Command Palette in VS Code&lt;/strong&gt;
Start by opening the Command Palette, which is the hub for accessing various commands in VS Code. You can open it by pressing &lt;code&gt;Cmd + Shift + P&lt;/code&gt; on your keyboard.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Access Keyboard Shortcuts File&lt;/strong&gt;
In the Command Palette, type &lt;code&gt;keyboard shortcuts json&lt;/code&gt;. This will bring up a list of options related to keyboard shortcuts. Select &lt;code&gt;Preference: Open Keyboard Shortcuts (JSON)&lt;/code&gt; from the list. This action opens the &lt;code&gt;keybindings.json&lt;/code&gt; file where you can define custom keyboard shortcuts.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Edit the JSON File&lt;/strong&gt;
In the &lt;code&gt;keybindings.json&lt;/code&gt; file, you will define the shortcut for the GitHub Copilot chat panel. Insert the following code snippet:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ctrl+alt+cmd+c&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;workbench.panel.chat.view.copilot.focus&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;when&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;editorFocus&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Move focus back to the Editor
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ctrl+alt+cmd+c&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;command&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;workbench.action.focusActiveEditorGroup&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;when&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;!editorFocus&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This configuration sets up &lt;code&gt;Ctrl + Alt + Command + C&lt;/code&gt; as the shortcut to toggle the GitHub Copilot chat panel. The first part focuses on the Copilot chat panel when the editor is focused, and the second part returns focus to the editor if the Copilot panel is focused.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Save and Test Your Shortcut&lt;/strong&gt;
After adding the code, save your &lt;code&gt;keybindings.json&lt;/code&gt; file. Test the new shortcut by pressing &lt;code&gt;Ctrl + Alt + Command + C&lt;/code&gt; in the editor. If everything is set up correctly, you should be able to toggle the GitHub Copilot chat panel with this shortcut.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="wrapping-it-up"&gt;
&lt;a href="#wrapping-it-up" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Wrapping it up
&lt;/h3&gt;
&lt;p&gt;After saving the binding, invoke it in the intended editor context and confirm that it opens the current Copilot Chat command without shadowing another shortcut.&lt;/p&gt;</description></item><item><title>Node.js Guide to package.json and package-lock.json</title><link>https://www.yopa.page/blog/2024-03-20-mastering-node.js-in-depth-guide-to-package.json-and-package-lock.json.html</link><pubDate>Wed, 20 Mar 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-03-20-mastering-node.js-in-depth-guide-to-package.json-and-package-lock.json.html</guid><description>
&lt;p&gt;In the realm of Node.js development, understanding the nuances of &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;package-lock.json&lt;/code&gt; is crucial for building and maintaining robust applications. These files are not just placeholders in your project; they are pivotal in defining, managing, and ensuring the consistency of your application&amp;rsquo;s dependencies across different environments. This article delves deep into the anatomy of &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;package-lock.json&lt;/code&gt;, underscores their critical roles, and provides a real-life scenario that showcases their utility.&lt;/p&gt;
&lt;h2 id="understanding-packagejson"&gt;
&lt;a href="#understanding-packagejson" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Understanding &lt;code&gt;package.json&lt;/code&gt;
&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;package.json&lt;/code&gt; file acts as the backbone of any Node.js project, offering a snapshot of your project&amp;rsquo;s metadata and dependencies. Here&amp;rsquo;s a detailed exploration:&lt;/p&gt;
&lt;h3 id="basic-purpose-of-packagejson"&gt;
&lt;a href="#basic-purpose-of-packagejson" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Basic Purpose of &lt;code&gt;package.json&lt;/code&gt;
&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Project Metadata&lt;/strong&gt;: It encapsulates essential details like your project&amp;rsquo;s name, version, and description, which are vital for both documentation and identification purposes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Dependency Management&lt;/strong&gt;: This is where &lt;code&gt;package.json&lt;/code&gt; truly shines, listing all necessary dependencies and devDependencies, thus allowing Node.js to fetch and install them appropriately.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Scripts&lt;/strong&gt;: It enables you to define script commands for various tasks such as starting your app or running tests, streamlining your development workflows.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;License Information&lt;/strong&gt;: Indicates the legal licensing under which the project is released.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="advanced-usage-of-packagejson"&gt;
&lt;a href="#advanced-usage-of-packagejson" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Advanced Usage of &lt;code&gt;package.json&lt;/code&gt;
&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Versioning and npm Workflows&lt;/strong&gt;: Embracing semantic versioning and utilizing npm&amp;rsquo;s capabilities for version management can significantly enhance your project&amp;rsquo;s reliability.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Custom Scripts&lt;/strong&gt;: Beyond standard scripts, you can introduce custom automation scripts for tasks like linting, building, or deployment.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Package Configuration&lt;/strong&gt;: Certain npm packages offer the option to be configured directly within &lt;code&gt;package.json&lt;/code&gt;, fostering a centralized configuration approach.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Peer Dependencies&lt;/strong&gt;: Essential for library authors, this section ensures consumers have the compatible dependency versions installed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Engines&lt;/strong&gt;: Specify which Node.js engine versions your project is compatible with, ensuring consistency across development and deployment environments.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Private Packages&lt;/strong&gt;: Mark your package as private to prevent unintentional publishing to npm.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="how-nodejs-installs-dependencies"&gt;
&lt;a href="#how-nodejs-installs-dependencies" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
How Node.js Installs Dependencies
&lt;/h3&gt;
&lt;p&gt;When dependencies are declared in &lt;code&gt;package.json&lt;/code&gt;, Node.js uses npm to install them as follows:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Parsing &lt;code&gt;package.json&lt;/code&gt;&lt;/strong&gt;: npm examines &lt;code&gt;package.json&lt;/code&gt; to identify the required dependencies and their specified versions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Resolving Dependencies&lt;/strong&gt;: npm determines the exact versions to install, adhering to the version ranges specified. It ensures that the installed versions meet the version constraints while considering the latest versions available.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Downloading and Installing&lt;/strong&gt;: npm fetches and installs the dependencies from the npm registry. The &lt;code&gt;dependencies&lt;/code&gt; are installed for production, and &lt;code&gt;devDependencies&lt;/code&gt; are additionally installed in development environments.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Storing Dependencies&lt;/strong&gt;: Installed packages are placed in the &lt;code&gt;node_modules&lt;/code&gt; directory within your project, allowing Node.js to access and load them as needed.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Version Locking&lt;/strong&gt;: npm generates a &lt;code&gt;package-lock.json&lt;/code&gt; to lock down the exact versions installed, ensuring consistent installs across environments.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="real-life-example"&gt;
&lt;a href="#real-life-example" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Real-Life Example
&lt;/h4&gt;
&lt;p&gt;Imagine a web application project that uses several dependencies, such as Express for the backend and React for the frontend. &lt;code&gt;package.json&lt;/code&gt; lists these dependencies with specified version ranges. When setting up the project on a new machine or deployment environment, npm consults &lt;code&gt;package.json&lt;/code&gt; to install the exact versions of these libraries, as recorded in &lt;code&gt;package-lock.json&lt;/code&gt;, ensuring that the application behaves consistently across different setups and avoiding the &amp;ldquo;works on my machine&amp;rdquo; problem.&lt;/p&gt;
&lt;h2 id="package-lockjson-ensuring-consistency"&gt;
&lt;a href="#package-lockjson-ensuring-consistency" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
package-lock.json: Ensuring Consistency
&lt;/h2&gt;
&lt;p&gt;Commit the root &lt;code&gt;package-lock.json&lt;/code&gt; for an npm-managed project and use &lt;code&gt;npm ci&lt;/code&gt; in CI when an exact clean install is required. Review &lt;code&gt;engines&lt;/code&gt;, &lt;code&gt;packageManager&lt;/code&gt;, workspaces, and overrides as explicit project policy; the lockfile is generated output and should not be hand-edited.&lt;/p&gt;
&lt;p&gt;While &lt;code&gt;package.json&lt;/code&gt; provides a broad overview and intent of your project&amp;rsquo;s dependencies, &lt;code&gt;package-lock.json&lt;/code&gt; nails down the exact versions of each installed package, ensuring that your project remains consistent and predictable across all installations. It records the specific version of each package that is installed and guarantees that the same version is used every time you (or anyone else) runs &lt;code&gt;npm install&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="best-practices-for-maintaining-package-lockjson"&gt;
&lt;a href="#best-practices-for-maintaining-package-lockjson" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Best Practices for Maintaining &lt;code&gt;package-lock.json&lt;/code&gt;
&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Version Control&lt;/strong&gt;: Always check in &lt;code&gt;package-lock.json&lt;/code&gt; into your version control system. This inclusion ensures that every collaborator or deployment environment uses precisely the same set of dependencies.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Regular Updates&lt;/strong&gt;: Dependencies should be updated methodically to incorporate critical security patches, bug fixes, or new features. &lt;code&gt;npm update&lt;/code&gt; can&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;be your ally here, modifying &lt;code&gt;package-lock.json&lt;/code&gt; to reflect updated versions.&lt;/p&gt;
&lt;ol start="3"&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Conflict Resolution&lt;/strong&gt;: In case of merge conflicts in &lt;code&gt;package-lock.json&lt;/code&gt;, it&amp;rsquo;s generally safe to regenerate it by deleting the existing file and running &lt;code&gt;npm install&lt;/code&gt;, ensuring alignment with &lt;code&gt;package.json&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Using npm ci&lt;/strong&gt;: In continuous integration environments, prefer &lt;code&gt;npm ci&lt;/code&gt; over &lt;code&gt;npm install&lt;/code&gt; to adhere strictly to the versions defined in &lt;code&gt;package-lock.json&lt;/code&gt;, enhancing the reliability of your builds.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Avoid Manual Edits&lt;/strong&gt;: You should not manually edit &lt;code&gt;package-lock.json&lt;/code&gt; as it can lead to inconsistencies. If there&amp;rsquo;s a need to change a version of a package, do it through &lt;code&gt;package.json&lt;/code&gt; and let npm update the lock file accordingly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Review Changes&lt;/strong&gt;: When &lt;code&gt;package-lock.json&lt;/code&gt; is updated (for instance, when adding a new package), review the changes to ensure that the versions are correct and there are no unexpected modifications.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;Grasping the intricacies of &lt;code&gt;package.json&lt;/code&gt; and &lt;code&gt;package-lock.json&lt;/code&gt; is fundamental for any Node.js developer aiming to build reliable, maintainable, and consistent applications. Do not be a developer, just execute &lt;code&gt;npm run all&lt;/code&gt; without knowing what it is about. Fundamental matters in every part of life so is this.&lt;/p&gt;</description></item><item><title>Testing Dependencies in Node.js Projects - A Focus on Package.json</title><link>https://www.yopa.page/blog/2024-03-19-testing-dependencies-in-node.js-projects-a-focus-on-package.json.html</link><pubDate>Tue, 19 Mar 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-03-19-testing-dependencies-in-node.js-projects-a-focus-on-package.json.html</guid><description>
&lt;p&gt;Dependency management is a critical factor in the development of Node.js projects, significantly influencing the reliability and maintainability of applications. For teams that maintain their own libraries in conjunction with main projects, the task of debugging and testing these dependencies can pose unique challenges. This article explores a situation where a Node.js library, referred to as &lt;code&gt;@yopa/the-library&lt;/code&gt;, requires comprehensive testing within a primary project to validate its functionality.&lt;/p&gt;
&lt;h3 id="the-challenge-debugging-internal-libraries"&gt;
&lt;a href="#the-challenge-debugging-internal-libraries" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Challenge: Debugging Internal Libraries
&lt;/h3&gt;
&lt;p&gt;Imagine you&amp;rsquo;re working on a Node.js project, and your application relies on an internal library you&amp;rsquo;ve developed and maintained. This library, referred to as &lt;code&gt;@yopa/the-library&lt;/code&gt;, is crucial for your application&amp;rsquo;s functionality. However, when issues arise in your main project, it&amp;rsquo;s essential to pinpoint whether the problem lies within the project itself or is due to the integrated library.&lt;/p&gt;
&lt;p&gt;Traditionally, this library is managed as a npm package and versioned something like &lt;code&gt;@yopa/the-library: ^1.12.10&lt;/code&gt;, which introduces complexity when attempting to debug interactions between the library and the main project. This becomes even more challenging when you need to modify the library code and test these changes directly within the main project.&lt;/p&gt;
&lt;h3 id="a-strategic-approach-to-dependency-testing"&gt;
&lt;a href="#a-strategic-approach-to-dependency-testing" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A Strategic Approach to Dependency Testing
&lt;/h3&gt;
&lt;p&gt;Prefer a workspace, a &lt;code&gt;file:&lt;/code&gt; dependency, or &lt;code&gt;npm pack&lt;/code&gt; to test the exact library artifact. &lt;code&gt;npm link&lt;/code&gt; is useful for live iteration but can change module resolution and produce duplicate singleton dependencies. Do not delete lockfiles or rewrite &lt;code&gt;package.json&lt;/code&gt; without preserving and reviewing the diff.&lt;/p&gt;
&lt;p&gt;To efficiently tackle this challenge, the following steps provide a systematic method for testing and debugging the library within the context of the main project:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Local Packaging of the Library&lt;/strong&gt;: Begin by making the necessary modifications to your library code. Once the changes are ready for testing, execute &lt;code&gt;npm pack&lt;/code&gt; in the library&amp;rsquo;s root directory. This command generates a &lt;code&gt;.tgz&lt;/code&gt; file, essentially a zipped version of your library, which you can then integrate directly into your main project.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Modifying &lt;code&gt;package.json&lt;/code&gt; Dependencies&lt;/strong&gt;: Navigate to the &lt;code&gt;package.json&lt;/code&gt; file in your main project. Update the dependency entry for &lt;code&gt;the-library&lt;/code&gt; to point to the absolute path of the &lt;code&gt;.tgz&lt;/code&gt; file you generated, rather than specifying a version number. This change directs npm to use the local version of the library for installation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Installation and Deployment&lt;/strong&gt;: With the dependency path updated, run &lt;code&gt;npm install&lt;/code&gt; to integrate the local version of your library into the main project.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Iterative Testing and Debugging&lt;/strong&gt;: As you continue to refine and test your library, you&amp;rsquo;ll need to update the &lt;code&gt;.tgz&lt;/code&gt; file regularly. This involves running &lt;code&gt;npm run build&lt;/code&gt; (assuming your library requires a build step) and &lt;code&gt;npm pack&lt;/code&gt; again to create a new package reflecting your latest changes. After updating the package, use &lt;code&gt;npm i ./path/to/tgzfile.tgz&lt;/code&gt; in your main project to test the updated library.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;By adopting this workflow, you can directly test how changes in your library affect the main project, enabling rapid iteration and more effective debugging.&lt;/p&gt;
&lt;h3 id="a-note-of-gratitude"&gt;
&lt;a href="#a-note-of-gratitude" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A Note of Gratitude
&lt;/h3&gt;
&lt;p&gt;In the realm of software development, collaboration and mutual learning are key to overcoming challenges and achieving success. I&amp;rsquo;d like to extend my sincere gratitude to my team members, Steven and Ryan, for their insights, support, and the collaborative spirit they bring to our projects. Working alongside such dedicated individuals is not only inspiring but also immensely educational.&lt;/p&gt;
&lt;p&gt;Wrapping it up&lt;/p&gt;
&lt;p&gt;By utilizing local packaging and strategic modifications in &lt;code&gt;package.json&lt;/code&gt;, developers can achieve a more integrated and responsive debugging environment.&lt;/p&gt;</description></item><item><title>Understanding Metadata API vs Tooling API in Salesforce Development</title><link>https://www.yopa.page/blog/2024-03-18-understanding-metadata-api-vs-tooling-api-in-salesforce-development.html</link><pubDate>Mon, 18 Mar 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-03-18-understanding-metadata-api-vs-tooling-api-in-salesforce-development.html</guid><description>
&lt;p&gt;When developing in Salesforce, understanding the nuanced differences between Metadata API and Tooling API can significantly streamline your workflow. Both are powerful interfaces for interacting with your org&amp;rsquo;s metadata, but they serve distinct purposes and are suited to different kinds of tasks. Let&amp;rsquo;s delve into the specifics of each API and explore real-life examples to guide you on when to use one over the other.&lt;/p&gt;
&lt;h2 id="metadata-api-configuration-lifecycle"&gt;
&lt;a href="#metadata-api-configuration-lifecycle" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Metadata API: Configuration Lifecycle
&lt;/h2&gt;
&lt;p&gt;Metadata API retrieves, deploys, creates, updates, or deletes supported customization metadata. Its defining concern is configuration lifecycle, not merely operation size.&lt;/p&gt;
&lt;h3 id="real-life-example-full-scale-deployment"&gt;
&lt;a href="#real-life-example-full-scale-deployment" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Real-Life Example: Full-Scale Deployment
&lt;/h3&gt;
&lt;p&gt;Imagine you&amp;rsquo;re working on a major update for your Salesforce org, which includes new custom objects, updated page layouts, several new fields, and modified permissions. Here, the Metadata API shines by helping you to package all these changes and deploy them from your sandbox environment to production efficiently. &lt;strong&gt;It&amp;rsquo;s akin to moving an entire library of books from one room to another, ensuring nothing is left behind.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="tooling-api-developer-tooling-objects"&gt;
&lt;a href="#tooling-api-developer-tooling-objects" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Tooling API: Developer Tooling Objects
&lt;/h2&gt;
&lt;p&gt;Tooling API exposes objects used by developer tools, including Apex execution, tests, coverage, traces, and selected development artifacts. It isn&amp;rsquo;t a universal fine-grained replacement for Metadata API.&lt;/p&gt;
&lt;h3 id="real-life-example-iterative-development"&gt;
&lt;a href="#real-life-example-iterative-development" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Real-Life Example: Iterative Development
&lt;/h3&gt;
&lt;p&gt;Consider a scenario where you&amp;rsquo;re developing a new custom application within Salesforce and need to frequently create, test, and modify individual Apex classes and Visualforce pages. The Tooling API is your best friend here, enabling quick iterations and immediate feedback on your changes. &lt;strong&gt;It&amp;rsquo;s like fine-tuning a single book&amp;rsquo;s layout and content, ensuring every page is perfect before the final print.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="choosing-the-right-api-for-your-task"&gt;
&lt;a href="#choosing-the-right-api-for-your-task" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Choosing the Right API for Your Task
&lt;/h2&gt;
&lt;p&gt;Choose by resource and lifecycle rather than size alone:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use Metadata API when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You&amp;rsquo;re deploying or retrieving supported configuration metadata.&lt;/li&gt;
&lt;li&gt;Your work involves complex metadata types and dependencies.&lt;/li&gt;
&lt;li&gt;You&amp;rsquo;re migrating changes across environments (e.g., sandbox to production).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use Tooling API when:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You&amp;rsquo;re building developer tooling around Apex, tests, traces, and coverage.&lt;/li&gt;
&lt;li&gt;You need Tooling API objects documented for the operation.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;For business records or application UI, evaluate REST, Composite, GraphQL, or UI API instead. Pin an API version, grant least privilege, handle limits, and test in a nonproduction org. Reviewed against Salesforce&amp;rsquo;s &lt;a href="https://developer.salesforce.com/docs/apis"&gt;official API documentation&lt;/a&gt; on 2026-08-01.&lt;/p&gt;</description></item><item><title>Mastering SF CLI - In-Depth Guide to sf project convert source and mdapi Commands</title><link>https://www.yopa.page/blog/2024-03-17-mastering-sf-cli-in-depth-guide-to-sf-project-convert-source-and-mdapi-commands.html</link><pubDate>Sun, 17 Mar 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-03-17-mastering-sf-cli-in-depth-guide-to-sf-project-convert-source-and-mdapi-commands.html</guid><description>
&lt;p&gt;&lt;img
src="./images/oni-salesforce-2.webp"
alt="Image not found: Salesforce CLI Conversion"
style="
background: #f0f0f0;
color: #666;
padding: 20px;
text-align: center;
border: 1px solid #ddd;
display: block;
"
/&gt;&lt;/p&gt;
&lt;p&gt;Salesforce CLI (Command Line Interface) continues to evolve, introducing sophisticated tools to enhance the efficiency and structure of Salesforce project management. Among the noteworthy updates are the &lt;code&gt;sf project convert source&lt;/code&gt; and &lt;code&gt;sf project convert mdapi&lt;/code&gt; commands, crucial for transitioning project formats and aiding developers in their deployment and development workflows. This article explores these commands in depth, examining their features, differences, and strategic importance in the Salesforce development landscape.&lt;/p&gt;
&lt;h2 id="exploring-sf-project-convert-source"&gt;
&lt;a href="#exploring-sf-project-convert-source" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Exploring &lt;code&gt;sf project convert source&lt;/code&gt;
&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;sf project convert source&lt;/code&gt; transforms source-formatted files into Metadata API format for consumers that explicitly require that representation. Ordinary Salesforce CLI deployments can deploy source format directly, so conversion is not a default deployment step.&lt;/p&gt;
&lt;h3 id="features-and-advantages"&gt;
&lt;a href="#features-and-advantages" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Features and Advantages:
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Preparation for Deployment&lt;/strong&gt;: It readies source-formatted files for deployment, ensuring they are compatible with Metadata API–dependent Salesforce environments.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Efficient Workflow Management&lt;/strong&gt;: By allowing developers to work in the Source format and convert files as needed, it supports a seamless transition from development to deployment.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="practical-examples"&gt;
&lt;a href="#practical-examples" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Practical Examples:
&lt;/h4&gt;
&lt;p&gt;This command allows for flexible directory and package specification, illustrated by these examples:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Converting a source directory to the metadata format: &lt;code&gt;$ sf project convert source --root-dir path/to/source&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Defining an output directory and package name: &lt;code&gt;$ sf project convert source --root-dir path/to/source --output-dir path/to/output --package-name 'My Package'&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="reevaluating-sf-project-convert-mdapi"&gt;
&lt;a href="#reevaluating-sf-project-convert-mdapi" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Reevaluating &lt;code&gt;sf project convert mdapi&lt;/code&gt;
&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;sf project convert mdapi&lt;/code&gt; command complements &lt;code&gt;sf project convert source&lt;/code&gt; by converting Metadata API format files back to the Source format, supporting a reverse workflow.&lt;/p&gt;
&lt;h3 id="features-and-advantages-1"&gt;
&lt;a href="#features-and-advantages-1" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Features and Advantages:
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Transition to Source Format&lt;/strong&gt;: It simplifies the move to Source format, beneficial for version control, collaboration, and CI/CD integration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Legacy Integration&lt;/strong&gt;: It&amp;rsquo;s crucial for developers migrating from Metadata API–based projects to the modular Source format.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Treat converted output as an ephemeral interoperability artifact. Keep one source of truth in Git and avoid editing both representations.&lt;/p&gt;
&lt;h3 id="comparing-metadata-api-and-source-format-structures"&gt;
&lt;a href="#comparing-metadata-api-and-source-format-structures" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Comparing Metadata API and Source Format Structures
&lt;/h3&gt;
&lt;p&gt;Understanding the structural differences between Metadata API and Source formats is essential for leveraging the full potential of the conversion commands.&lt;/p&gt;
&lt;h4 id="metadata-api-format-illustration"&gt;
&lt;a href="#metadata-api-format-illustration" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Metadata API Format Illustration:
&lt;/h4&gt;
&lt;p&gt;The Metadata API format groups similar types of metadata, potentially complicating detailed version control. An example structure is as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plaintext" data-lang="plaintext"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;unpackaged/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── classes
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── MyApexClass.cls
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── MyApexClass.cls-meta.xml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── objects
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── MyCustomObject__c.object
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── AnotherObject__c.object
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── pages
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── MyVisualforcePage.page
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── package.xml
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h4 id="source-format-illustration"&gt;
&lt;a href="#source-format-illustration" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Source Format Illustration:
&lt;/h4&gt;
&lt;p&gt;The Source format, on the other hand, offers a granular component breakdown, aiding in precise change management and conflict resolution:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-plaintext" data-lang="plaintext"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;force-app/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;├── main/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── default/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── classes/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ ├── MyApexClass.cls
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ └── MyApexClass.cls-meta.xml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ ├── objects/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ ├── MyCustomObject__c/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ │ ├── fields/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ │ │ └── MyField__c.field-meta.xml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ │ └── MyCustomObject__c.object-meta.xml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ └── AnotherObject__c/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ │ └── AnotherObject__c.object-meta.xml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── pages/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;│ └── MyVisualforcePage.page-meta.xml
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;└── sfdx-project.json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;These distinctions highlight the strategic considerations in choosing the appropriate command based on the project&amp;rsquo;s phase and requirements.&lt;/p&gt;
&lt;h2 id="wrapping-it-up-"&gt;
&lt;a href="#wrapping-it-up-" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Wrapping it up 👏
&lt;/h2&gt;
&lt;p&gt;Use &lt;code&gt;sf project deploy preview&lt;/code&gt; and &lt;code&gt;sf project deploy start --dry-run&lt;/code&gt; before direct deployment. Convert only at a legacy-tool or package boundary, write to a clean build directory, and verify the resulting &lt;code&gt;package.xml&lt;/code&gt;. Reviewed against the &lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-reference/guide/cli_reference.html"&gt;Salesforce CLI reference&lt;/a&gt; on 2026-08-01.&lt;/p&gt;</description></item><item><title>How to Fix Divergent Branch Problems in Git - A Step-by-Step Guide.</title><link>https://www.yopa.page/blog/2024-03-12-how-to-fix-divergent-branch-problems-in-git.html</link><pubDate>Tue, 12 Mar 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-03-12-how-to-fix-divergent-branch-problems-in-git.html</guid><description>
&lt;p&gt;&lt;img
src="./images/github-watching-you.webp"
alt="Image not found: Git Branching"
style="
background: #f0f0f0;
color: #666;
padding: 20px;
text-align: center;
border: 1px solid #ddd;
display: block;
"
/&gt;&lt;/p&gt;
&lt;p&gt;When working with Git, you might encounter a situation where your branches have diverged, leading to confusion and potential merge conflicts. This blog post will delve into what causes divergent branches in Git and how to reconcile them effectively.&lt;/p&gt;
&lt;h2 id="understanding-divergent-branches-in-git"&gt;
&lt;a href="#understanding-divergent-branches-in-git" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Understanding Divergent Branches in Git
&lt;/h2&gt;
&lt;p&gt;Divergent branches occur when your &lt;strong&gt;local branch and the corresponding remote branch have different commits&lt;/strong&gt;. This situation typically arises when multiple people are working on the same branch or when changes are made to the remote branch after you&amp;rsquo;ve cloned or fetched it but before you try to push your local changes.&lt;/p&gt;
&lt;p&gt;When you attempt to &lt;code&gt;git pull&lt;/code&gt;, Git may show a message indicating that you have divergent branches and need to specify how to reconcile them. This is because Git wants to prevent unintentional loss of work and ensure that you are making a conscious decision about how to integrate the changes.&lt;/p&gt;
&lt;h2 id="strategies-to-reconcile-divergent-branches"&gt;
&lt;a href="#strategies-to-reconcile-divergent-branches" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Strategies to Reconcile Divergent Branches
&lt;/h2&gt;
&lt;p&gt;There are three main strategies to reconcile divergent branches in Git:&lt;/p&gt;
&lt;h3 id="1-merge"&gt;
&lt;a href="#1-merge" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1. Merge
&lt;/h3&gt;
&lt;p&gt;Merging is the most straightforward approach. It combines the histories of the two branches, creating a new merge commit. This approach maintains the full history of both branches but can lead to a more complex commit history.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git config pull.rebase &lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git pull origin &amp;lt;branch-name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="2-rebase"&gt;
&lt;a href="#2-rebase" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2. Rebase
&lt;/h3&gt;
&lt;p&gt;Rebasing rewrites the commit history by applying your local changes on top of the remote branch&amp;rsquo;s latest commit. It results in a cleaner, linear history but should be used with caution as it changes commit hashes.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git config pull.rebase &lt;span class="nb"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git pull origin &amp;lt;branch-name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;details&gt;
&lt;summary&gt;The Significance of the Rebase Warning&lt;/summary&gt;
&lt;p&gt;The warning &amp;ldquo;but should be used with caution as it changes commit hashes&amp;rdquo; during a rebase is crucial to understand. When you rebase a branch, Git essentially creates new commits for each commit in your branch since it diverged from the base branch. This process changes the commit hashes, which are the unique identifiers for each commit.&lt;/p&gt;
&lt;h4 id="why-is-this-important"&gt;
&lt;a href="#why-is-this-important" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why is this Important?
&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Collaboration Impact&lt;/strong&gt;: If you&amp;rsquo;ve already pushed your branch and then rebase it, the rebased branch will not align with the remote branch since the commit hashes have changed. If someone else is working on the same branch, they will encounter conflicts when they try to pull your changes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Data Integrity&lt;/strong&gt;: Changing commit hashes can also impact any references to those commits, such as in submodules, or when using commit hashes in documentation or issue tracking.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Force Pushing&lt;/strong&gt;: After a rebase, you&amp;rsquo;ll need to force push (&lt;code&gt;git push --force&lt;/code&gt;) to update the remote branch with the rewritten history. Force pushing can be risky as it can overwrite changes on the remote branch, so it should be done with caution and clear communication among team members.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;h3 id="3-fast-forward-only"&gt;
&lt;a href="#3-fast-forward-only" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
3. Fast-forward only
&lt;/h3&gt;
&lt;p&gt;The fast-forward only approach refuses to merge if the local branch has diverged from the remote branch, ensuring that your history only ever moves forward linearly from the base.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git config pull.ff only
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git pull origin &amp;lt;branch-name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="setting-a-default-reconciliation-strategy"&gt;
&lt;a href="#setting-a-default-reconciliation-strategy" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Setting a Default Reconciliation Strategy
&lt;/h2&gt;
&lt;p&gt;You can set your preferred strategy globally or per repository, providing flexibility and control over your project&amp;rsquo;s version control strategy.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Set globally&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git config --global pull.rebase &lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Set for a specific repository&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git config pull.rebase &lt;span class="nb"&gt;false&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You can set this setting globally by adding &lt;code&gt;--global&lt;/code&gt; to the git config command, but it&amp;rsquo;s generally safer to set them &lt;strong&gt;per-repository&lt;/strong&gt; (which I prefer) unless you are certain about this.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;Understanding how to manage divergent branches in Git is critical for maintaining a clean workflow in your development. By choosing the appropriate strategy to reconcile branches, you can ensure that your project&amp;rsquo;s history is manageable, reducing potential integration headaches down the line. (ultimate goal!)&lt;/p&gt;
&lt;h2 id="conclusion-1"&gt;
&lt;a href="#conclusion-1" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;Whether you prefer merging, rebasing, or fast-forwarding, Git provides the ways you need to maintain a consistent history in your version control.&lt;/p&gt;</description></item><item><title>Choosing a Python Project and Dependency Workflow</title><link>https://www.yopa.page/blog/2026-08-01-choosing-python-project-workflow.html</link><pubDate>Mon, 11 Mar 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-choosing-python-project-workflow.html</guid><description>
&lt;p&gt;Python&amp;rsquo;s environment and packaging ecosystem offers several valid workflows. Moving from &lt;code&gt;venv&lt;/code&gt; to Pipenv is not automatically an upgrade: &lt;code&gt;venv&lt;/code&gt; creates an isolated environment, while workflow tools may additionally resolve dependencies, create locks, build packages, publish artifacts, or run tasks. Start with the capabilities a repository needs.&lt;/p&gt;
&lt;p&gt;This guide was verified on 2026-08-01 against the &lt;a href="https://packaging.python.org/en/latest/guides/tool-recommendations/"&gt;Python Packaging User Guide tool recommendations&lt;/a&gt;, the &lt;a href="https://docs.python.org/3/library/venv.html"&gt;&lt;code&gt;venv&lt;/code&gt; documentation&lt;/a&gt;, and the &lt;a href="https://packaging.python.org/en/latest/specifications/"&gt;reproducible-environments specifications&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="current-mental-model"&gt;
&lt;a href="#current-mental-model" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Current mental model
&lt;/h2&gt;
&lt;p&gt;Separate four concerns:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;An interpreter manager, such as pyenv, selects a Python release.&lt;/li&gt;
&lt;li&gt;An environment manager isolates installed packages. Standard &lt;code&gt;venv&lt;/code&gt; is the minimal baseline.&lt;/li&gt;
&lt;li&gt;A resolver selects compatible dependency versions.&lt;/li&gt;
&lt;li&gt;A build backend creates a wheel or source distribution when the project is packaged.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;A single tool may cover several concerns, but the project should still document which files are authoritative and which outputs are regenerated.&lt;/p&gt;
&lt;h2 id="a-safe-minimal-baseline"&gt;
&lt;a href="#a-safe-minimal-baseline" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A safe minimal baseline
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 -m venv .venv
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m pip install --upgrade pip
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m pip install -r requirements.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m pip check
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m pytest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Use &lt;code&gt;python -m pip&lt;/code&gt; to make the target interpreter explicit. Keep &lt;code&gt;.venv/&lt;/code&gt; out of Git. For an application that requires fully repeatable deployments, add the repository&amp;rsquo;s chosen lock or compiled-constraints workflow rather than relying on an old environment&amp;rsquo;s &lt;code&gt;pip freeze&lt;/code&gt; output.&lt;/p&gt;
&lt;h2 id="choosing-among-tools"&gt;
&lt;a href="#choosing-among-tools" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Choosing among tools
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;venv&lt;/code&gt; plus pip is broadly available and easy to debug, but does not prescribe one lock workflow.&lt;/li&gt;
&lt;li&gt;pip-tools compiles reviewed requirement inputs into pinned outputs and fits many existing pip deployments.&lt;/li&gt;
&lt;li&gt;Pipenv manages a virtual environment and &lt;code&gt;Pipfile.lock&lt;/code&gt;; keep it when the team and automation already depend on that contract.&lt;/li&gt;
&lt;li&gt;Poetry and PDM provide project metadata, resolution, locking, and packaging workflows with different conventions.&lt;/li&gt;
&lt;li&gt;Hatch focuses on project environments, builds, publishing, and task-like scripts.&lt;/li&gt;
&lt;li&gt;uv provides fast environment and dependency workflows and supports several modern Python packaging standards.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tool popularity is not a migration requirement. Evaluate supported Python versions, platform behavior, private index authentication, lock portability, CI installation, editor support, and maintainer continuity.&lt;/p&gt;
&lt;h2 id="migration-without-deleting-evidence"&gt;
&lt;a href="#migration-without-deleting-evidence" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration without deleting evidence
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Record the current interpreter, direct dependency declarations, lock or constraints files, and successful test commands.&lt;/li&gt;
&lt;li&gt;Create a new branch and a separate clean environment. Do not delete the working environment yet.&lt;/li&gt;
&lt;li&gt;Configure the candidate tool from direct project intent, not by declaring every transitive package as direct.&lt;/li&gt;
&lt;li&gt;Resolve and inspect changes in versions, hashes, indexes, and platform markers.&lt;/li&gt;
&lt;li&gt;Run unit, integration, build, and smoke tests on every supported platform.&lt;/li&gt;
&lt;li&gt;Update CI and onboarding documentation in the same change.&lt;/li&gt;
&lt;li&gt;Prove a clean clone can reproduce the workflow before removing the old files.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Never make &lt;code&gt;rm -rf venv&lt;/code&gt; the first migration step. An environment is disposable only after its requirements and working verification commands are preserved.&lt;/p&gt;
&lt;h2 id="application-versus-library-tradeoffs"&gt;
&lt;a href="#application-versus-library-tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Application versus library tradeoffs
&lt;/h2&gt;
&lt;p&gt;Applications often lock the complete deployable environment. Libraries generally declare compatible dependency ranges so downstream applications can resolve them. Test a library against its supported range rather than publishing an application-style lock as a consumer requirement.&lt;/p&gt;
&lt;p&gt;For standalone Python commands, use &lt;code&gt;pipx&lt;/code&gt; to isolate the installed application. For CI publishing to PyPI, prefer trusted publishing where supported rather than a long-lived upload token.&lt;/p&gt;
&lt;h2 id="verification-gate"&gt;
&lt;a href="#verification-gate" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Verification gate
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -c &lt;span class="s1"&gt;&amp;#39;import sys; print(sys.executable, sys.version)&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m pip check
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m build
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m pytest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Use only commands relevant to the repository, but require a clean-clone installation and the same production entry point used before migration. Keep the old workflow available until the new path passes those gates and the rollback is understood.&lt;/p&gt;
&lt;h2 id="operational-checks-after-migration"&gt;
&lt;a href="#operational-checks-after-migration" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Operational checks after migration
&lt;/h2&gt;
&lt;p&gt;A resolver change can affect installation time, cache behavior, artifact size, private-index requests, and failure messages in CI. Confirm that dependency automation can still update the authoritative declaration and that developers do not need personal credentials in committed configuration. Test an unavailable index so bootstrap fails clearly instead of using an unexplained global package.&lt;/p&gt;
&lt;p&gt;Document how to add, remove, and update a direct dependency; how to regenerate resolved output; and which files reviewers inspect. Include the supported Python matrix and the command that proves the active interpreter. A future teammate should reproduce the environment from a clean checkout without knowing which tool was popular when the repository was created.&lt;/p&gt;</description></item><item><title>Update Notifications in Node.js with TypeScript</title><link>https://www.yopa.page/blog/2024-02-26-mastering-update-notifier-in-node.js-with-typescript.html</link><pubDate>Mon, 26 Feb 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-02-26-mastering-update-notifier-in-node.js-with-typescript.html</guid><description>
&lt;p&gt;&lt;img
src="./images/oni-red-flag.webp"
alt="Image not found: TypeScript with Update-Notifier"
style="
background: #f0f0f0;
color: #666;
padding: 20px;
text-align: center;
border: 1px solid #ddd;
display: block;
"
/&gt;&lt;/p&gt;
&lt;p&gt;Keeping your Node.js application up-to-date is crucial for security, performance, and feature enhancements. The &lt;code&gt;update-notifier&lt;/code&gt; npm package, when used with TypeScript, provides a powerful yet straightforward way to notify users of your application about new updates. This blog delves into the integration of &lt;code&gt;update-notifier&lt;/code&gt; within a TypeScript environment, covering both basic and advanced usages with detailed code examples.&lt;/p&gt;
&lt;h2 id="introduction-to-update-notifier-with-typescript"&gt;
&lt;a href="#introduction-to-update-notifier-with-typescript" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Introduction to Update-Notifier with TypeScript
&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;update-notifier&lt;/code&gt; is a handy tool for Node.js applications to check for available updates and notify users, ensuring they are always using the latest version. Integrating it with TypeScript adds type safety and enhances code readability and maintainability.&lt;/p&gt;
&lt;h3 id="setting-up-your-typescript-project"&gt;
&lt;a href="#setting-up-your-typescript-project" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Setting Up Your TypeScript Project
&lt;/h3&gt;
&lt;p&gt;Before diving into &lt;code&gt;update-notifier&lt;/code&gt;, ensure your TypeScript environment is set up. You&amp;rsquo;ll need a &lt;code&gt;tsconfig.json&lt;/code&gt; file configured for your project. Here&amp;rsquo;s a basic setup:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;compilerOptions&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;target&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;es5&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;module&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;commonjs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;strict&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;esModuleInterop&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;resolveJsonModule&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;resolveJsonModule&lt;/code&gt; option in &lt;code&gt;compilerOptions&lt;/code&gt; allows TypeScript to natively import JSON modules, such as your &lt;code&gt;package.json&lt;/code&gt; file. This is crucial for &lt;code&gt;update-notifier&lt;/code&gt; to access the current version and package name.&lt;/p&gt;
&lt;h3 id="installing-update-notifier"&gt;
&lt;a href="#installing-update-notifier" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Installing Update-Notifier
&lt;/h3&gt;
&lt;p&gt;Current &lt;code&gt;update-notifier&lt;/code&gt; releases include TypeScript declarations and are ESM. Confirm the package version and Node module mode before using the examples. Notifications should be disabled in CI and non-interactive output, and should never block the command.&lt;/p&gt;
&lt;p&gt;To add &lt;code&gt;update-notifier&lt;/code&gt; to your project, run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm install update-notifier
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The second command installs the TypeScript type definitions for &lt;code&gt;update-notifier&lt;/code&gt;, allowing for type checking and IntelliSense in your IDE.&lt;/p&gt;
&lt;h2 id="basic-usage-in-typescript"&gt;
&lt;a href="#basic-usage-in-typescript" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Basic Usage in TypeScript
&lt;/h2&gt;
&lt;p&gt;After ensuring your &lt;code&gt;tsconfig.json&lt;/code&gt; is properly configured to import JSON modules, you can now easily import your &lt;code&gt;package.json&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;updateNotifier&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;update-notifier&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;pkg&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./package.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;updateNotifier&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;updateCheckInterval&lt;/span&gt;: &lt;span class="kt"&gt;1000&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This script will check for updates once a day and notify the user when there&amp;rsquo;s a new version available.&lt;/p&gt;
&lt;h2 id="advanced-usage-examples"&gt;
&lt;a href="#advanced-usage-examples" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Advanced Usage Examples
&lt;/h2&gt;
&lt;h3 id="prompting-users-to-update"&gt;
&lt;a href="#prompting-users-to-update" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Prompting Users to Update
&lt;/h3&gt;
&lt;p&gt;You can prompt users interactively to update their application using &lt;code&gt;inquirer&lt;/code&gt; alongside &lt;code&gt;update-notifier&lt;/code&gt;. Here&amp;rsquo;s a TypeScript example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;updateNotifier&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;update-notifier&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;inquirer&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;inquirer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;pkg&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./package.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;execSync&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;child_process&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;updateNotifier&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;pkg&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;inquirer&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;confirm&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;update&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`Update available: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;latest&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;. Do you want to update now?`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;answers&lt;/span&gt;: &lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;answers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;execSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`npm install -g &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;stdio&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;inherit&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="custom-notifications-based-on-update-type"&gt;
&lt;a href="#custom-notifications-based-on-update-type" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Custom Notifications Based on Update Type
&lt;/h3&gt;
&lt;p&gt;You can customize the notification message based on the update type (major, minor, patch) using TypeScript&amp;rsquo;s enum and type checking features:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;updateNotifier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;UpdateInfo&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;update-notifier&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;pkg&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./package.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;updateNotifier&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;pkg&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt;: &lt;span class="kt"&gt;UpdateInfo&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;major&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;A new major version is available. Updating is highly recommended!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;minor&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;A new minor version is available. Update to access new features.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;patch&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;A new patch version is available. Update to improve stability.&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="logging-update-details"&gt;
&lt;a href="#logging-update-details" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Logging Update Details
&lt;/h3&gt;
&lt;p&gt;For more detailed logging, especially useful in debugging or maintaining logs, you can utilize TypeScript to ensure structured and type-safe logging:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;updateNotifier&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;update-notifier&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;pkg&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./package.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;notifier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;updateNotifier&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;pkg&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Update available: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;latest&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Current version: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;pkg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;version&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Update type: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;notifier&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;Update notifications should be a non-blocking convenience. Respect CI and non-interactive environments, provide an opt-out where appropriate, and do not make network availability a requirement for the command to succeed.&lt;/p&gt;</description></item><item><title>Structured CLI Tasks with Listr2</title><link>https://www.yopa.page/blog/2024-02-19-enhance-cli-with-listr-a-node-js-task-manager.html</link><pubDate>Mon, 19 Feb 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-02-19-enhance-cli-with-listr-a-node-js-task-manager.html</guid><description>
&lt;p&gt;&lt;img
src="./images/oni-speaker.webp"
alt="Image not found: Listr Task List Example"
style="
background: #f0f0f0;
color: #666;
padding: 20px;
text-align: center;
border: 1px solid #ddd;
display: block;
"
/&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Introduction&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Listr2 presents structured progress for multi-step CLI operations. Use it when task hierarchy helps users understand progress, while retaining plain and complete output for CI and logs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Why Listr?&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Elegant Task Displays:&lt;/strong&gt; Unlike traditional CLI outputs, Listr introduces clean layouts, spinners, and status symbols that significantly enhance visual appeal and user comprehension.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Organized Workflows:&lt;/strong&gt; Listr allows for the breaking down of complex tasks into manageable subtasks, reflecting the logical structure of operations and facilitating easier troubleshooting.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Progress Tracking:&lt;/strong&gt; Real-time updates provided by Listr foster trust and a sense of responsiveness, keeping users informed about the script&amp;rsquo;s execution status.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Getting Started (with TypeScript)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Before diving into Listr, ensure you have a compatible Node.js environment. Listr&amp;rsquo;s TypeScript support offers strong typing benefits, making your task management scripts more maintainable and error-resistant.&lt;/p&gt;
&lt;p&gt;The original &lt;code&gt;listr&lt;/code&gt; package is superseded by &lt;code&gt;listr2&lt;/code&gt;, which includes its own TypeScript types. Pin a compatible release and configure a non-interactive renderer for CI, logs, and redirected output. Progress UI must not hide command failures.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Install Listr and types:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm install listr @types/listr
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;A simple TypeScript example:&lt;/strong&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Listr&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;listr2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ListrTask&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;listr2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Define a context for your tasks to use
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;MyContext&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;downloadPath&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Listr&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;MyContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;([&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Download project assets&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;downloadAssets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;downloadPath&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Here, downloadAssets is a function you define to download project assets
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Process data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;processData&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// processData is another function for handling your data
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Run the tasks with a specified context
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;run&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;downloadPath&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;/tmp&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Listr&amp;rsquo;s Flexibility in Action&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Error Handling:&lt;/strong&gt;
Efficient error management is crucial for resilient script execution. Listr enables specific error handling routines and rollback mechanisms to maintain stability.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Listr&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;MyContext&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;([&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Fetch sensitive data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="c1"&gt;// fetchData should safely handle sensitive information
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;onRollback&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;cleanUpSensitiveFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tempFiles&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// cleanUpSensitiveFiles is a custom cleanup function
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Observability:&lt;/strong&gt;
Tracking task progress is simplified with Listr&amp;rsquo;s event hooks, offering insights into each step&amp;rsquo;s execution.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Listr&lt;/span&gt;&lt;span class="p"&gt;([...],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;rendererOptions&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;showSubtasks&lt;/span&gt;: &lt;span class="kt"&gt;false&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;task:start&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Starting task: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Customization:&lt;/strong&gt;
Tailor Listr&amp;rsquo;s output to meet your needs, whether for a concise summary or detailed logs.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Listr&lt;/span&gt;&lt;span class="p"&gt;([...],&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;renderer&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;verbose&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;strong&gt;Real-World Use Cases&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Deploying Listr in practical scenarios demonstrates its versatility and impact:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Build Script Example:&lt;/strong&gt;
Simplify your build process with structured tasks, making each step transparent and manageable.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Clean&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;runCleanCommand&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Transpile Code (TypeScript)&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;transpileCode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Bundle Assets&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;bundleAssets&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Deployment Workflow Example:&lt;/strong&gt;
Streamline deployments by organizing tasks sequentially, ensuring each step is completed before proceeding.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Test Suite&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;runTestSuite&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Push to Staging&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;deployToStaging&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Monitor&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;task&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;monitorLogs&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;enabled&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deploySuccess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;**Wrapping it up **&lt;/p&gt;
&lt;p&gt;Listr2 is useful when interactive progress materially improves a multi-step command. Review its current &lt;a href="https://github.com/cenk1cenk2/listr2"&gt;official documentation&lt;/a&gt;, and keep the underlying task functions independent from the renderer.&lt;/p&gt;</description></item><item><title>Mastering Salesforce CLI with Grep - A Guide to Finding Commands Efficiently</title><link>https://www.yopa.page/blog/2024-02-11-mastering-salesforce-cli-with-grep-a-guide-to-finding-commands-efficiently.html</link><pubDate>Sun, 11 Feb 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-02-11-mastering-salesforce-cli-with-grep-a-guide-to-finding-commands-efficiently.html</guid><description>
&lt;p&gt;&lt;img
src="./images/oni-salesforce-1.webp"
alt="Image not found: Salesforce CLI and Oni"
style="
background: #f0f0f0;
color: #666;
padding: 20px;
text-align: center;
border: 1px solid #ddd;
display: block;
"
/&gt;&lt;/p&gt;
&lt;p&gt;Salesforce CLI now has built-in discovery commands. Start with &lt;code&gt;sf search&lt;/code&gt;, &lt;code&gt;sf commands&lt;/code&gt;, command help, shell completion, and &lt;code&gt;sf which&lt;/code&gt;; use &lt;code&gt;grep&lt;/code&gt; only as a fallback for narrowing text output.&lt;/p&gt;
&lt;h3 id="1-finding-basic-commands"&gt;
&lt;a href="#1-finding-basic-commands" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1. Finding Basic Commands
&lt;/h3&gt;
&lt;p&gt;Starting with the basics, let&amp;rsquo;s say you need to find commands related to organizations (&lt;code&gt;orgs&lt;/code&gt;). The Salesforce CLI has various org-related commands, but to find them quickly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org --help &lt;span class="p"&gt;|&lt;/span&gt; grep &lt;span class="s2"&gt;&amp;#34;org&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This command filters the extensive help information of &lt;code&gt;sf org&lt;/code&gt; to lines containing &amp;ldquo;org&amp;rdquo;, giving you a quick overview of available org commands.&lt;/p&gt;
&lt;h3 id="2-locating-commands-for-data-manipulation"&gt;
&lt;a href="#2-locating-commands-for-data-manipulation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2. Locating Commands for Data Manipulation
&lt;/h3&gt;
&lt;p&gt;Often, you&amp;rsquo;ll need to manipulate data, like querying or exporting data. For instance, to find commands related to querying data:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf data --help &lt;span class="p"&gt;|&lt;/span&gt; grep &lt;span class="s2"&gt;&amp;#34;query&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This reveals options and subcommands under &lt;code&gt;sf data&lt;/code&gt; that are related to querying, making it easier to find the exact syntax or command you need.&lt;/p&gt;
&lt;h3 id="3-searching-for-deployment-commands"&gt;
&lt;a href="#3-searching-for-deployment-commands" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
3. Searching for Deployment Commands
&lt;/h3&gt;
&lt;p&gt;Deployment is a critical task, and you need to get the commands right. If you&amp;rsquo;re looking for commands related to deploying metadata:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf project deploy start --help &lt;span class="p"&gt;|&lt;/span&gt; grep -i &lt;span class="s2"&gt;&amp;#34;test\|target&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This filters out the specific options and subcommands for metadata deployment, simplifying the task of finding the right command for your deployment needs.&lt;/p&gt;
&lt;h3 id="4-user-management-commands"&gt;
&lt;a href="#4-user-management-commands" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
4. User Management Commands
&lt;/h3&gt;
&lt;p&gt;Managing users is a common task. To find commands related to user creation or management:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf user --help &lt;span class="p"&gt;|&lt;/span&gt; grep &lt;span class="s2"&gt;&amp;#34;create&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This command is particularly useful when you&amp;rsquo;re looking to create new users and need to know the specific command and its options.&lt;/p&gt;
&lt;h3 id="5-environment-and-instance-management"&gt;
&lt;a href="#5-environment-and-instance-management" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
5. Environment and Instance Management
&lt;/h3&gt;
&lt;p&gt;For managing your environments or instances, you might need specific commands. To find those:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf env --help &lt;span class="p"&gt;|&lt;/span&gt; grep &lt;span class="s2"&gt;&amp;#34;list&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This is helpful to list all environments or instances, providing you with the exact command to use.&lt;/p&gt;
&lt;h3 id="advanced-tips"&gt;
&lt;a href="#advanced-tips" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Advanced Tips
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Case-Insensitive Search&lt;/strong&gt;: Use &lt;code&gt;grep -i&lt;/code&gt; for a case-insensitive search, which is particularly useful if you&amp;rsquo;re unsure about the casing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Extended Patterns&lt;/strong&gt;: Use &lt;code&gt;egrep&lt;/code&gt; or &lt;code&gt;grep -E&lt;/code&gt; for more complex pattern searches.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Contextual Search&lt;/strong&gt;: If you need more context around your search, use &lt;code&gt;grep&lt;/code&gt; with &lt;code&gt;-B&lt;/code&gt;, &lt;code&gt;-A&lt;/code&gt;, or &lt;code&gt;-C&lt;/code&gt; to display lines before, after, or around the matching line.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="a-better-discovery-sequence"&gt;
&lt;a href="#a-better-discovery-sequence" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A better discovery sequence
&lt;/h3&gt;
&lt;p&gt;Run &lt;code&gt;sf search&lt;/code&gt;, inspect &lt;code&gt;sf &amp;lt;topic&amp;gt; --help&lt;/code&gt;, then open the exact command&amp;rsquo;s help. Use &lt;code&gt;sf which &amp;lt;command&amp;gt;&lt;/code&gt; to identify its plugin and &lt;code&gt;sf commands&lt;/code&gt; for an inventory. &lt;code&gt;grep&lt;/code&gt; is Unix-specific and can hide surrounding context, so documentation and built-in help remain the source of truth. Verified against the &lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-plugin/guide/super-powers.html"&gt;Salesforce CLI discovery guide&lt;/a&gt; on 2026-08-01.&lt;/p&gt;</description></item><item><title>Testing TypeScript with Jest</title><link>https://www.yopa.page/blog/2024-02-10-mastering-jest-with-typescript-a-comprehensive-guide.html</link><pubDate>Sat, 10 Feb 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-02-10-mastering-jest-with-typescript-a-comprehensive-guide.html</guid><description>
&lt;p&gt;&lt;img
src="./images/oni-jest.webp"
alt="Image not found: Jest and Oni"
style="
background: #f0f0f0;
color: #666;
padding: 20px;
text-align: center;
border: 1px solid #ddd;
display: block;
"
/&gt;&lt;/p&gt;
&lt;p&gt;In the realm of JavaScript and TypeScript development, testing your code is just as important as writing it. Jest, a delightful JavaScript Testing Framework, has gained immense popularity for its simplicity and feature-rich environment. When combined with TypeScript, it brings strong typing and helps catch errors early, leading to more reliable and maintainable code. In this article, we dive deep into how to use Jest with TypeScript, exploring its powerful features through practical examples.&lt;/p&gt;
&lt;h2 id="setting-up-jest-with-typescript"&gt;
&lt;a href="#setting-up-jest-with-typescript" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Setting up Jest with TypeScript
&lt;/h2&gt;
&lt;p&gt;This setup uses &lt;code&gt;ts-jest&lt;/code&gt; and Jest transforms. Native ESM projects require different configuration and mocking rules; follow the current &lt;a href="https://jestjs.io/docs/ecmascript-modules"&gt;Jest ESM guide&lt;/a&gt; instead of copying CommonJS assumptions.&lt;/p&gt;
&lt;p&gt;Before harnessing the power of Jest in a TypeScript environment, you need to set up your project correctly. Start by installing TypeScript, Jest, and the necessary dependencies:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm install --save-dev typescript jest @types/jest ts-jest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Then, initialize TypeScript in your project:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npx tsc --init
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Configure Jest to work with TypeScript by editing your Jest configuration file (&lt;code&gt;jest.config.js&lt;/code&gt;):&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;preset&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ts-jest&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;testEnvironment&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;node&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="writing-your-first-jest-test-in-typescript"&gt;
&lt;a href="#writing-your-first-jest-test-in-typescript" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Writing Your First Jest Test in TypeScript
&lt;/h2&gt;
&lt;p&gt;With the setup ready, let&amp;rsquo;s dive into writing a basic test. Consider a simple function in &lt;code&gt;sum.ts&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// sum.ts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You can write a test for this function in &lt;code&gt;sum.test.ts&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// sum.test.ts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./sum&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;adds 1 + 2 to equal 3&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Run your tests using:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npx jest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="exploring-matchers-in-jest"&gt;
&lt;a href="#exploring-matchers-in-jest" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Exploring Matchers in Jest
&lt;/h2&gt;
&lt;p&gt;Matchers are the heart of Jest&amp;rsquo;s testing power, allowing you to write various assertions.&lt;/p&gt;
&lt;h3 id="common-matchers"&gt;
&lt;a href="#common-matchers" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Common Matchers
&lt;/h3&gt;
&lt;p&gt;Matchers in Jest are methods that let you test values in different ways. These methods are used with the &lt;code&gt;expect&lt;/code&gt; function to assert whether a particular value meets certain conditions. Jest offers a wide range of matchers, allowing you to handle various scenarios in your tests, from simple equality checks to more complex checks like testing for exceptions or checking array contents.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;toBe&lt;/code&gt; and &lt;code&gt;toEqual&lt;/code&gt;&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;toBe&lt;/code&gt; uses &lt;code&gt;Object.is&lt;/code&gt; to test exact equality. It&amp;rsquo;s great for primitive types.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toEqual&lt;/code&gt; recursively checks every field of an object or array.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;two plus two is four&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;object assignment&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;number&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;one&lt;/span&gt;: &lt;span class="kt"&gt;1&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;two&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;one&lt;/span&gt;: &lt;span class="kt"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;two&lt;/span&gt;: &lt;span class="kt"&gt;2&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Truthiness&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;toBeNull&lt;/code&gt; matches only &lt;code&gt;null&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toBeUndefined&lt;/code&gt; matches only &lt;code&gt;undefined&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toBeDefined&lt;/code&gt; is the opposite of &lt;code&gt;toBeUndefined&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toBeTruthy&lt;/code&gt; matches anything that an &lt;code&gt;if&lt;/code&gt; statement treats as true.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;toBeFalsy&lt;/code&gt; matches anything that an &lt;code&gt;if&lt;/code&gt; statement treats as false.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;null&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;n&lt;/span&gt;: &lt;span class="kt"&gt;null&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeNull&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeDefined&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toBeUndefined&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toBeTruthy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeFalsy&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Numbers&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Matchers like &lt;code&gt;toBeGreaterThan&lt;/code&gt;, &lt;code&gt;toBeLessThan&lt;/code&gt;, etc., are used for comparing numbers.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;two plus two&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeGreaterThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeLessThan&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Strings&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;toMatch&lt;/code&gt; is used to check strings against regular expressions.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;there is no I in team&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;team&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toMatch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/I/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Arrays and Iterables&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;toContain&lt;/code&gt; checks if an array or iterable contains a particular item.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;the shopping list has beer on it&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shoppingList&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;diapers&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;kleenex&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;trash bags&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;beer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shoppingList&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toContain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;beer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Exceptions&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;toThrow&lt;/code&gt; checks if a function throws an error when it&amp;rsquo;s called.&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;compileAndroidCode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;you are using the wrong JDK&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;compiling android goes as expected&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compileAndroidCode&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toThrow&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compileAndroidCode&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compileAndroidCode&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;you are using the wrong JDK&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;compileAndroidCode&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/JDK/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="custom-matchers"&gt;
&lt;a href="#custom-matchers" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Custom Matchers
&lt;/h3&gt;
&lt;p&gt;Jest allows you to extend its matchers by using &lt;code&gt;expect.extend&lt;/code&gt; to add your custom matchers. This is useful when you want to add reusable test logic, or if you&amp;rsquo;re testing something specific to your application.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Extend the expect type with a custom matcher
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kr"&gt;global&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;namespace&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Matchers&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;R&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;toBeDivisibleBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;argument&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;R&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;toBeDivisibleBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;received&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;argument&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;pass&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;received&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nx"&gt;argument&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;pass&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sb"&gt;`expected &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;received&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; not to be divisible by &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;argument&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;pass&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="sb"&gt;`expected &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;received&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; to be divisible by &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;argument&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;pass&lt;/span&gt;: &lt;span class="kt"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;even and odd numbers&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBeDivisibleBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;101&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;not&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toBeDivisibleBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Matchers are a powerful part of Jest, providing a rich vocabulary for writing tests. They help make your tests expressive and ensure that your code behaves as expected. The variety and extensibility of Jest matchers make them suitable for a wide range of testing scenarios.&lt;/p&gt;
&lt;h2 id="setup-and-teardown"&gt;
&lt;a href="#setup-and-teardown" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Setup and Teardown
&lt;/h2&gt;
&lt;p&gt;Jest offers several functions that allow you to perform setup and teardown tasks in your tests. These functions are &lt;code&gt;beforeAll&lt;/code&gt;, &lt;code&gt;beforeEach&lt;/code&gt;, &lt;code&gt;afterAll&lt;/code&gt;, and &lt;code&gt;afterEach&lt;/code&gt;. They are particularly useful for preparing the environment for tests and cleaning up afterward. Here&amp;rsquo;s a detailed look at each of these functions:&lt;/p&gt;
&lt;h3 id="1-beforeall"&gt;
&lt;a href="#1-beforeall" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1. &lt;code&gt;beforeAll&lt;/code&gt;
&lt;/h3&gt;
&lt;p&gt;This function runs once before all the tests in a describe block. It&amp;rsquo;s useful for setting up something that is needed by all the tests and is expensive to set up, like a database connection or a large dataset.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;beforeAll&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Setup code that runs once before all tests
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="2-beforeeach"&gt;
&lt;a href="#2-beforeeach" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2. &lt;code&gt;beforeEach&lt;/code&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;beforeEach&lt;/code&gt; runs before each test in a describe block. This is useful for resetting conditions to a known state before every test, such as resetting database records or clearing mocks.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Setup code that runs before each test
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="3-afterall"&gt;
&lt;a href="#3-afterall" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
3. &lt;code&gt;afterAll&lt;/code&gt;
&lt;/h3&gt;
&lt;p&gt;This function is called once after all the tests in a describe block have completed. It is used for cleanup activities that need to happen after all tests have run, like closing database connections or freeing up resources that were used during the tests.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;afterAll&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Cleanup code that runs once after all tests
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="4-aftereach"&gt;
&lt;a href="#4-aftereach" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
4. &lt;code&gt;afterEach&lt;/code&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;afterEach&lt;/code&gt; is executed after each test in a describe block. It&amp;rsquo;s often used for cleanup that should be performed after each test, like resetting mock functions or clearing changes made during the individual test.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Cleanup code that runs after each test
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="example-usage"&gt;
&lt;a href="#example-usage" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Example Usage
&lt;/h3&gt;
&lt;p&gt;Here&amp;rsquo;s an example that demonstrates how these functions might be used in a test suite:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Test Suite&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;beforeAll&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// This will run once before all the tests in this suite
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Assuming initializeDatabase is a function you&amp;#39;ve defined
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;initializeDatabase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;beforeEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// This will run before every test
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Assuming resetTestData is a function you&amp;#39;ve defined
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;resetTestData&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Test 1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Test code here
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Example: expect(someFunction()).toBe(someValue);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Test 2&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Test code here
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Example: expect(someOtherFunction()).toEqual(someOtherValue);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;afterEach&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// This will run after each test
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Assuming clearMocks is a function you&amp;#39;ve defined
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;clearMocks&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;afterAll&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// This will run once after all the tests in this suite
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Assuming closeDatabaseConnection is a function you&amp;#39;ve defined
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;closeDatabaseConnection&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, &lt;code&gt;initializeDatabase&lt;/code&gt; and &lt;code&gt;closeDatabaseConnection&lt;/code&gt; are used for setting up and tearing down the database, which is a relatively heavy operation, so it&amp;rsquo;s done once. &lt;code&gt;resetTestData&lt;/code&gt; and &lt;code&gt;clearMocks&lt;/code&gt; are lighter operations needed to ensure each test runs in a clean state, so they are done before and after each test.&lt;/p&gt;
&lt;p&gt;These functions help in organizing the test code and managing resources efficiently, making the tests more reliable and maintainable.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;Jest, when used with TypeScript, provides a robust framework for writing and managing your tests. Its rich set of matchers, combined with TypeScript&amp;rsquo;s strong typing, ensures that your tests are both powerful and maintainable.&lt;/p&gt;</description></item><item><title>Choose a Salesforce Package Model: 1GP, Managed 2GP, or Unlocked</title><link>https://www.yopa.page/blog/2026-08-01-choose-a-salesforce-package-model.html</link><pubDate>Mon, 05 Feb 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-choose-a-salesforce-package-model.html</guid><description>
&lt;p&gt;Verified against Salesforce documentation on 2026-08-01.&lt;/p&gt;
&lt;h2 id="why-the-old-comparison-was-archived"&gt;
&lt;a href="#why-the-old-comparison-was-archived" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why the old comparison was archived
&lt;/h2&gt;
&lt;p&gt;Packaging isn&amp;rsquo;t a linear upgrade from 1GP to 2GP. The earlier article incorrectly suggested that a namespace-free package could later be promoted into a managed package and described dependencies as flexible version ranges. Package type, namespace, ancestry, and version dependencies are lifecycle decisions with constraints.&lt;/p&gt;
&lt;h2 id="decision-model"&gt;
&lt;a href="#decision-model" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Decision model
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Maintain an existing managed 1GP product when migration cost and compatibility outweigh 2GP benefits.&lt;/li&gt;
&lt;li&gt;Choose managed 2GP for a new commercial or partner-distributed application that needs namespacing, upgrade lineage, and AppExchange lifecycle.&lt;/li&gt;
&lt;li&gt;Choose unlocked packages for internal modular delivery where source control is authoritative and subscribers control installation.&lt;/li&gt;
&lt;li&gt;Use unmanaged packages only for templates or one-time sharing, not an upgradeable product.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Managed packages require deliberate namespace and Dev Hub planning. An unlocked package without a namespace does not become a managed package by promotion. Dependencies identify package versions; validate the exact dependency graph instead of assuming arbitrary semantic ranges.&lt;/p&gt;
&lt;h2 id="safe-workflow"&gt;
&lt;a href="#safe-workflow" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Safe workflow
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf package create --name BillingCore --package-type Unlocked &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --path force-app --target-dev-hub dev-hub
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf package version create --package BillingCore &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --installation-key-bypass --wait &lt;span class="m"&gt;20&lt;/span&gt; --target-dev-hub dev-hub
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf package version promote --package 04t... --target-dev-hub dev-hub
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf package install --package 04t... --target-org &lt;span class="nb"&gt;test&lt;/span&gt; --wait &lt;span class="m"&gt;20&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Never promote before installing the version in a clean org and running Apex, permission, upgrade, and dependency tests. Protect installation keys and do not print auth URLs in CI.&lt;/p&gt;
&lt;h2 id="tradeoffs"&gt;
&lt;a href="#tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Tradeoffs
&lt;/h2&gt;
&lt;p&gt;Unlocked packages favor internal autonomy but don&amp;rsquo;t provide the same intellectual-property and subscriber controls as managed packages. Managed 2GP improves source-driven releases but makes namespace, ancestry, and version choices durable. 1GP remains valid for established products, although new development loses many 2GP workflow benefits. Direct metadata deployment is simpler for a small org but provides no package installation boundary.&lt;/p&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Inventory subscribers, namespace, package IDs, dependencies, and upgrade promises.&lt;/li&gt;
&lt;li&gt;Decide internal versus external distribution and ownership of upgrades.&lt;/li&gt;
&lt;li&gt;Prototype the chosen model in a separate Dev Hub and disposable org.&lt;/li&gt;
&lt;li&gt;Define package directories and dependency versions in &lt;code&gt;sfdx-project.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Test fresh install, upgrade, uninstall behavior, permissions, and data migration.&lt;/li&gt;
&lt;li&gt;Promote only an immutable version that passed the release gate.&lt;/li&gt;
&lt;li&gt;Document rollback; package promotion itself isn&amp;rsquo;t a rollback mechanism.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="verification-and-sources"&gt;
&lt;a href="#verification-and-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Verification and sources
&lt;/h2&gt;
&lt;p&gt;Confirm the package/version IDs in the Dev Hub, install into an empty org, upgrade from the oldest supported version, and run the complete test suite.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_dev2gp.htm"&gt;Package Types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_unlocked_pkg_workflow.htm"&gt;Unlocked Package Workflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-reference/guide/cli_reference_package.html"&gt;Salesforce CLI package commands&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Git Diff for Working Trees, Commits, and Branches</title><link>https://www.yopa.page/blog/2024-01-27-mastering-git-diff-a-comprehensive-guide-with-advanced-examples.html</link><pubDate>Sat, 27 Jan 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-01-27-mastering-git-diff-a-comprehensive-guide-with-advanced-examples.html</guid><description>
&lt;p&gt;&lt;img
src="./images/oni-github-1.webp"
alt="Image not found: oni-github"
style="
background: #f0f0f0;
color: #666;
padding: 20px;
text-align: center;
border: 1px solid #ddd;
display: block;
"
/&gt;&lt;/p&gt;
&lt;p&gt;In the realm of version control with Git, understanding the &lt;code&gt;git diff&lt;/code&gt; command is crucial for developers. This post not only explores the basics but also dives into advanced uses of &lt;code&gt;git diff&lt;/code&gt;, complete with detailed examples and code blocks for a comprehensive understanding.&lt;/p&gt;
&lt;h2 id="basic-usage-of-git-diff"&gt;
&lt;a href="#basic-usage-of-git-diff" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Basic Usage of Git Diff
&lt;/h2&gt;
&lt;h3 id="1-viewing-uncommitted-changes"&gt;
&lt;a href="#1-viewing-uncommitted-changes" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;1. Viewing Uncommitted Changes&lt;/strong&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;git diff&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This command shows the differences between your working directory and the index (staging area). It&amp;rsquo;s helpful to review changes before staging them.&lt;/p&gt;
&lt;h4 id="example"&gt;
&lt;a href="#example" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;Example&lt;/strong&gt;:
&lt;/h4&gt;
&lt;p&gt;Suppose &lt;code&gt;file.txt&lt;/code&gt; originally contains &amp;ldquo;Hello, World!&amp;rdquo; and you add &amp;ldquo;New line&amp;rdquo; to it. Running &lt;code&gt;git diff&lt;/code&gt; will show:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;diff --git a/file.txt b/file.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;index ce01362..b6fc4c8 &lt;span class="m"&gt;100644&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;--- a/file.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+++ b/file.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;@@ -1 +1,2 @@
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Hello, World!
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;+New line
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="2-viewing-changes-that-are-staged-for-commit"&gt;
&lt;a href="#2-viewing-changes-that-are-staged-for-commit" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;2. Viewing Changes That Are Staged for Commit&lt;/strong&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;git diff --staged&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This compares your staged changes with the last commit, useful for a final review before committing.&lt;/p&gt;
&lt;h3 id="3-comparing-two-files-within-the-working-directory"&gt;
&lt;a href="#3-comparing-two-files-within-the-working-directory" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;3. Comparing Two Files Within the Working Directory&lt;/strong&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;git diff -- file1.txt file2.txt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This compares two local files, showing differences even if they aren&amp;rsquo;t tracked by Git.&lt;/p&gt;
&lt;h2 id="advanced-usage-of-git-diff"&gt;
&lt;a href="#advanced-usage-of-git-diff" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Advanced Usage of Git Diff
&lt;/h2&gt;
&lt;h3 id="1-difference-between-two-branches"&gt;
&lt;a href="#1-difference-between-two-branches" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;1. Difference Between Two Branches&lt;/strong&gt;
&lt;/h3&gt;
&lt;p&gt;Use &lt;code&gt;base...topic&lt;/code&gt; to inspect changes introduced since the merge base, as a pull request usually does. Use &lt;code&gt;base..topic&lt;/code&gt; only when an endpoint tree comparison is intended.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;git diff main..feature-branch&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This shows the changes between the &lt;code&gt;main&lt;/code&gt; branch and &lt;code&gt;feature-branch&lt;/code&gt;, useful for reviewing changes before merging branches.&lt;/p&gt;
&lt;h4 id="example-1"&gt;
&lt;a href="#example-1" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;Example&lt;/strong&gt;:
&lt;/h4&gt;
&lt;p&gt;Suppose in your &lt;code&gt;main&lt;/code&gt; branch, you have a file &lt;code&gt;hello.txt&lt;/code&gt; with &amp;ldquo;Hello, World!&amp;rdquo;, and in your &lt;code&gt;feature-branch&lt;/code&gt;, it&amp;rsquo;s changed to &amp;ldquo;Hello, Git World!&amp;rdquo;. The command &lt;code&gt;git diff main..feature-branch&lt;/code&gt; will show the differences.&lt;/p&gt;
&lt;h3 id="2-difference-between-two-commits"&gt;
&lt;a href="#2-difference-between-two-commits" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;2. Difference Between Two Commits&lt;/strong&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;git diff &amp;lt;HASH-OLDER-COMMIT&amp;gt;..&amp;lt;HASH-NEWER-COMMIT&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This is for comparing specific commits, perhaps to understand the changes introduced in a feature branch.&lt;/p&gt;
&lt;h3 id="3-diff-a-file-across-branches-or-commits"&gt;
&lt;a href="#3-diff-a-file-across-branches-or-commits" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;3. Diff a File Across Branches or Commits&lt;/strong&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;git diff main feature-branch file.txt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This focuses on how a single file (&lt;code&gt;file.txt&lt;/code&gt;) differs between the &lt;code&gt;main&lt;/code&gt; branch and &lt;code&gt;feature-branch&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="4-diff-a-file-over-time-in-the-same-branch"&gt;
&lt;a href="#4-diff-a-file-over-time-in-the-same-branch" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;4. Diff a File Over Time in the Same Branch&lt;/strong&gt;
&lt;/h3&gt;
&lt;p&gt;First step: &lt;code&gt;git log --before=&amp;quot;2023-01-01&amp;quot; --after=&amp;quot;2022-01-01&amp;quot; --follow -- file.txt&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Then: &lt;code&gt;git diff &amp;lt;old-commit-hash&amp;gt;..&amp;lt;new-commit-hash&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This combination is great for tracking a file&amp;rsquo;s evolution over a specified period.&lt;/p&gt;
&lt;h3 id="5-diff-a-file-that-has-been-renamed"&gt;
&lt;a href="#5-diff-a-file-that-has-been-renamed" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;5. Diff a File That Has Been Renamed&lt;/strong&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;git diff &amp;lt;old-commit-hash&amp;gt;..&amp;lt;new-commit-hash&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Git automatically tracks renamed files post Git 2.9, making it easier to see changes to files even after renaming.&lt;/p&gt;
&lt;h2 id="advanced-features"&gt;
&lt;a href="#advanced-features" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Advanced Features
&lt;/h2&gt;
&lt;h3 id="combining-diffs"&gt;
&lt;a href="#combining-diffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;Combining Diffs&lt;/strong&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;git diff &amp;lt;commit1&amp;gt;..&amp;lt;commit2&amp;gt;..&amp;lt;commit3&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This aggregates changes from multiple commits into one view, simplifying code review or audits.&lt;/p&gt;
&lt;h3 id="diff-with-context"&gt;
&lt;a href="#diff-with-context" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;Diff with Context&lt;/strong&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;git diff --unified=5 &amp;lt;commit1&amp;gt;..&amp;lt;commit2&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This shows more surrounding lines (context) around changes for better understanding.&lt;/p&gt;
&lt;h3 id="word-diff"&gt;
&lt;a href="#word-diff" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;Word Diff&lt;/strong&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;git diff --word-diff &amp;lt;commit1&amp;gt;..&amp;lt;commit2&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;It breaks down the differences to the word level, offering a more granular view.&lt;/p&gt;
&lt;h3 id="ignoring-space-changes"&gt;
&lt;a href="#ignoring-space-changes" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;Ignoring Space Changes&lt;/strong&gt;
&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;git diff -w &amp;lt;commit1&amp;gt;..&amp;lt;commit2&amp;gt;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;This ignores white space changes, focusing only on actual content changes.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;Use &lt;code&gt;git diff&lt;/code&gt; by first choosing the two states you intend to compare: working tree, index, commit endpoints, or a merge base. Review path exclusions carefully so important generated or policy files remain visible.&lt;/p&gt;</description></item><item><title>How to Check if Google Analytics is Working</title><link>https://www.yopa.page/blog/2024-01-25-how-to-check-if-google-analytics-is-working.html</link><pubDate>Thu, 25 Jan 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-01-25-how-to-check-if-google-analytics-is-working.html</guid><description>
&lt;p&gt;&lt;img
src="./images/oni-google.webp"
alt="Image not found: oni-google-ish&amp;hellip;"
style="
background: #f0f0f0;
color: #666;
padding: 20px;
text-align: center;
border: 1px solid #ddd;
display: block;
"
/&gt;&lt;/p&gt;
&lt;p&gt;Ensuring that your Google Analytics is set up correctly is crucial for accurately tracking your website&amp;rsquo;s traffic. While the Realtime report in Google Analytics provides immediate data about active users on your site, it doesn&amp;rsquo;t guarantee the absence of issues like duplicate tracking codes, which can distort your data significantly.&lt;/p&gt;
&lt;p&gt;To accurately check your website for such discrepancies, Google offers a powerful tool: the &lt;strong&gt;Google Tag Assistant&lt;/strong&gt;. This tool is not just a basic checker; it&amp;rsquo;s an essential instrument for anyone serious about accurate data analytics.&lt;/p&gt;
&lt;h2 id="how-to-use-google-tag-assistant-tool"&gt;
&lt;a href="#how-to-use-google-tag-assistant-tool" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
How to Use Google Tag Assistant Tool
&lt;/h2&gt;
&lt;p&gt;Google Tag Assistant allows you to identify and fix issues such as duplicate tracking codes, ensuring that your data reflects actual user behavior. Here&amp;rsquo;s how you can use this tool to verify your Google Analytics setup:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Access Tag Assistant&lt;/strong&gt;: Start by visiting &lt;a href="https://tagassistant.google.com"&gt;tagassistant.google.com&lt;/a&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Choose Your Assessment Method&lt;/strong&gt;: You have two options: assess a domain directly on the site or use the Tag Assistant Companion browser extension in Chrome for a more integrated experience.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;img
src="./images/Tag-assistant.webp"
alt="Image not found: Add the Domain"
style="
background: #f0f0f0;
color: #666;
padding: 20px;
text-align: center;
border: 1px solid #ddd;
display: block;
"
/&gt; 3. &lt;strong&gt;Add Your Domain&lt;/strong&gt;: Click the &amp;lsquo;Add domain&amp;rsquo; button to start the assessment process for your specific domain.&lt;/p&gt;
&lt;p&gt;&lt;img
src="./images/Connect-domain.webp"
alt="Image not found: Connect the Domain"
style="
background: #f0f0f0;
color: #666;
padding: 20px;
text-align: center;
border: 1px solid #ddd;
display: block;
"
/&gt; 4. &lt;strong&gt;Connect Your Domain&lt;/strong&gt;: After clicking &amp;lsquo;Connect&amp;rsquo;, your website will open in a new window. Don&amp;rsquo;t close this window; it&amp;rsquo;s crucial for the assessment process.&lt;/p&gt;
&lt;p&gt;&lt;img
src="./images/Tag-Assistant-tab.webp"
alt="Image not found: Tag Assistant Tab"
style="
background: #f0f0f0;
color: #666;
padding: 20px;
text-align: center;
border: 1px solid #ddd;
display: block;
"
/&gt; 5. &lt;strong&gt;Review Installed Tags&lt;/strong&gt;: Return to the Tag Assistant tab to review the tags installed on your site. This is where you can see if you have any duplicate or incorrect implementations. In our example, we noticed a UA container and a &lt;strong&gt;Google Analytics 4&lt;/strong&gt; container.&lt;/p&gt;
&lt;p&gt;By following these steps, you&amp;rsquo;re not just checking if Google Analytics is working; you&amp;rsquo;re ensuring that it&amp;rsquo;s working correctly. The Tag Assistant tool is a definitive way to verify your setup, giving you confidence in the data you&amp;rsquo;re using to make important business decisions.&lt;/p&gt;
&lt;h2 id="wrapping-it-up-"&gt;
&lt;a href="#wrapping-it-up-" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Wrapping it up 👏
&lt;/h2&gt;
&lt;p&gt;Accurate data is the cornerstone of effective web analytics. By utilizing the Google Tag Assistant, you&amp;rsquo;re taking a crucial step towards ensuring the integrity of your data. Remember, in the world of analytics, precision matters. Don&amp;rsquo;t just track; track correctly. Cheers to data-driven success!&lt;/p&gt;
&lt;p&gt;Cheers! 🍺&lt;/p&gt;</description></item><item><title>Understanding Git Revert vs. Manual Reversion - Best Practices for Code Rollbacks</title><link>https://www.yopa.page/blog/2024-01-24-understanding-git-revert-vs-manual-reversion-best-practices-for-code-rollbacks.html</link><pubDate>Wed, 24 Jan 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-01-24-understanding-git-revert-vs-manual-reversion-best-practices-for-code-rollbacks.html</guid><description>
&lt;p&gt;&lt;img
src="./images/oni-github.webp"
alt="Image not found: oni-github"
style="
background: #f0f0f0;
color: #666;
padding: 20px;
text-align: center;
border: 1px solid #ddd;
display: block;
"
/&gt;&lt;/p&gt;
&lt;p&gt;When it comes to undoing changes in your codebase, Git offers powerful tools that help maintain stability and traceability in your projects. Two common methods for reverting changes are using the &lt;code&gt;git revert&lt;/code&gt; command and manually reverting through a series of commands including &lt;code&gt;git checkout&lt;/code&gt;. Both methods have their uses, but understanding their nuances is key to choosing the right approach for your situation. In this article, we will explore each method, complete with examples, to help you make informed decisions when you need to roll back changes. Let&amp;rsquo;s go.&lt;/p&gt;
&lt;h2 id="method-1-using-git-revert"&gt;
&lt;a href="#method-1-using-git-revert" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Method 1: Using Git Revert
&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;git revert&lt;/code&gt; command is the standard way to undo changes in Git. It creates a new commit that reverses the changes made by a previous commit. This method doesn&amp;rsquo;t alter the project&amp;rsquo;s history, making it safe and transparent, especially in collaborative environments.&lt;/p&gt;
&lt;h3 id="example-of-git-revert"&gt;
&lt;a href="#example-of-git-revert" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Example of Git Revert
&lt;/h3&gt;
&lt;p&gt;Imagine you&amp;rsquo;ve made a commit that unexpectedly breaks your application. The commit ID is &lt;code&gt;abc1234&lt;/code&gt;. To revert this commit, you would use the following commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git revert abc1234
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This sequence of commands creates a new commit that undoes the changes made in commit &lt;code&gt;abc1234&lt;/code&gt;, then pushes this new commit to your remote repository.&lt;/p&gt;
&lt;h3 id="benefits-of-git-revert"&gt;
&lt;a href="#benefits-of-git-revert" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Benefits of Git Revert
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Preserves History&lt;/strong&gt;: Keeps the original commit in the history, alongside the revert commit, maintaining a complete and traceable record.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Collaboration Friendly&lt;/strong&gt;: Clearly communicates the reversion to team members, maintaining clarity in shared repositories.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="method-2-restore-selected-paths-and-commit-the-correction"&gt;
&lt;a href="#method-2-restore-selected-paths-and-commit-the-correction" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Method 2: Restore selected paths and commit the correction
&lt;/h2&gt;
&lt;p&gt;The manual reversion method involves checking out the state of a specific commit, manually resetting your files to that state, and then creating a new commit with those changes. This method gives you more control but requires careful handling to ensure clarity and traceability.&lt;/p&gt;
&lt;h3 id="example-of-manual-reversion"&gt;
&lt;a href="#example-of-manual-reversion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Example of Manual Reversion
&lt;/h3&gt;
&lt;p&gt;Suppose you want to revert to the state of your project at commit &lt;code&gt;def5678&lt;/code&gt;. You would use the following sequence of commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git restore --source&lt;span class="o"&gt;=&lt;/span&gt;def5678 --staged --worktree -- path/to/file
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add path/to/file
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;Manually reverting to the state of commit def5678&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This sequence manually reverts your project to the state of commit &lt;code&gt;def5678&lt;/code&gt; and then pushes this new state to your remote repository.&lt;/p&gt;
&lt;h3 id="benefits-of-manual-reversion"&gt;
&lt;a href="#benefits-of-manual-reversion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Benefits of Manual Reversion
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;More Control&lt;/strong&gt;: Allows you to manually adjust the state of your files, offering flexibility in complex scenarios where an automatic revert might not suffice.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Hands-On Approach&lt;/strong&gt;: Useful for quick rollbacks locally or when you want to carefully craft the state of your project.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="choosing-the-right-method"&gt;
&lt;a href="#choosing-the-right-method" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Choosing the Right Method
&lt;/h2&gt;
&lt;p&gt;While both methods can revert changes, choosing the right one depends on your specific needs:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;git revert&lt;/code&gt; when&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You&amp;rsquo;re working in a collaborative environment.&lt;/li&gt;
&lt;li&gt;You need to maintain a clear and traceable history.&lt;/li&gt;
&lt;li&gt;The changes you&amp;rsquo;re reverting are straightforward and don&amp;rsquo;t require manual intervention.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Consider manual reversion when&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The changes you need to revert are complex or require manual intervention.&lt;/li&gt;
&lt;li&gt;You&amp;rsquo;re working locally and need a quick rollback, and you&amp;rsquo;re confident about managing your project&amp;rsquo;s history.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;Whether you choose the automated &lt;code&gt;git revert&lt;/code&gt; or the manual checkout and commit approach, understanding the implications of each method on your project&amp;rsquo;s history and collaboration dynamics is key. Use &lt;code&gt;git revert&lt;/code&gt; for its clarity and traceability, especially in collaborative settings. Opt for manual reversion when you need more control over the process. Remember, the goal is not just to undo changes, but to do so in a way that maintains the integrity and understandability of your project history.&lt;/p&gt;</description></item><item><title>Resolve CloudFront CNAMEAlreadyExists Without an Avoidable Outage</title><link>https://www.yopa.page/blog/2024-01-23-navigating-the-cnamealreadyexists-error-in-aws-cloudfront.html</link><pubDate>Tue, 23 Jan 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-01-23-navigating-the-cnamealreadyexists-error-in-aws-cloudfront.html</guid><description>
&lt;p&gt;CloudFront returns &lt;code&gt;CNAMEAlreadyExists&lt;/code&gt; when the alternate domain name you want is already associated with another CloudFront distribution or distribution tenant. DNS may still point at the old resource, but deleting the DNS record alone does not necessarily remove the CloudFront association and can create an avoidable outage.&lt;/p&gt;
&lt;h2 id="prepare-the-target-first"&gt;
&lt;a href="#prepare-the-target-first" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Prepare the target first
&lt;/h2&gt;
&lt;p&gt;The target distribution must be deployed and include a valid TLS certificate from ACM in &lt;code&gt;us-east-1&lt;/code&gt;. The certificate&amp;rsquo;s subject alternative names must cover the domain you are moving. Configure origins, cache behavior, security policy, logging, and error responses before changing production traffic.&lt;/p&gt;
&lt;p&gt;Confirm the AWS account and distribution IDs:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws sts get-caller-identity --profile production
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws cloudfront get-distribution --id TARGET_ID --profile production
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Do not copy a domain between accounts based on a partial distribution ID from an error message.&lt;/p&gt;
&lt;h2 id="find-the-conflict"&gt;
&lt;a href="#find-the-conflict" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Find the conflict
&lt;/h2&gt;
&lt;p&gt;AWS recommends the newer &lt;code&gt;ListDomainConflicts&lt;/code&gt; operation because it supports both standard distributions and distribution tenants. &lt;code&gt;ListConflictingAliases&lt;/code&gt; supports standard distributions and remains relevant for older workflows. The caller needs permission for the operation and access to the target resource used in the ownership check.&lt;/p&gt;
&lt;p&gt;Use the current console, SDK, or AWS CLI command available in your installed CLI version. Inspect exact and wildcard conflicts: &lt;code&gt;*.example.com&lt;/code&gt; can overlap &lt;code&gt;www.example.com&lt;/code&gt; even when the literal alias is not obvious.&lt;/p&gt;
&lt;p&gt;If you own both resources and they are in the same account, update the source and target through the documented move procedure. Depending on resource type and account boundary, AWS supports &lt;code&gt;AssociateAlias&lt;/code&gt; or &lt;code&gt;UpdateDomainAssociation&lt;/code&gt;. Cross-account movement requires domain ownership proof, commonly through the documented DNS TXT record, and appropriate permissions on both sides.&lt;/p&gt;
&lt;h2 id="avoid-dns-first-troubleshooting"&gt;
&lt;a href="#avoid-dns-first-troubleshooting" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Avoid DNS-first troubleshooting
&lt;/h2&gt;
&lt;p&gt;Lower DNS TTL ahead of a planned move when appropriate, but do not remove a working record simply to clear the API error. CloudFront&amp;rsquo;s alias association and public DNS are separate state. Prepare and validate the target, move the association with the supported operation, update DNS if the target hostname changes, and keep the source available through propagation and cache windows.&lt;/p&gt;
&lt;p&gt;Test the target before the public cutover where possible:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;curl --resolve www.example.com:443:TARGET_EDGE_IP &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; https://www.example.com/health
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Because CloudFront edge IPs change and direct testing has caveats, follow the documented target-validation method for the migration type. Verify certificate hostname, response headers, origin routing, redirects, cache keys, cookies, and authenticated paths—not only the home page.&lt;/p&gt;
&lt;h2 id="failure-and-rollback"&gt;
&lt;a href="#failure-and-rollback" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Failure and rollback
&lt;/h2&gt;
&lt;p&gt;If AWS says the source is in another account you do not control, do not attempt to seize it. Verify DNS ownership and open an AWS Support case using the official process. Never publish account IDs, distribution details, or certificate validation records in a public ticket.&lt;/p&gt;
&lt;p&gt;Keep the source configuration and DNS values recorded. A rollback may require moving the alias back; DNS reversal alone is insufficient if CloudFront now associates the alias with the target. Monitor CloudFront 4xx/5xx, origin errors, TLS failures, cache hit behavior, and application checks during the change window.&lt;/p&gt;
&lt;h2 id="checklist"&gt;
&lt;a href="#checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Checklist
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Confirm account, source, target, exact alias, and wildcard overlap.&lt;/li&gt;
&lt;li&gt;Deploy target behavior and an &lt;code&gt;us-east-1&lt;/code&gt; certificate covering the alias.&lt;/li&gt;
&lt;li&gt;Use the supported conflict-list and association/move operation.&lt;/li&gt;
&lt;li&gt;Prove ownership for cross-account movement.&lt;/li&gt;
&lt;li&gt;Coordinate alias and DNS changes; do not delete DNS as a first fix.&lt;/li&gt;
&lt;li&gt;Validate critical paths and monitoring before and after cutover.&lt;/li&gt;
&lt;li&gt;Preserve a tested reverse-move rollback plan.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Official documentation reviewed on &lt;strong&gt;2026-08-01&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/alternate-domain-names-move.html"&gt;Move an alternate domain name&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/reference/cloudfront/list-domain-conflicts.html"&gt;&lt;code&gt;list-domain-conflicts&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/CNAMEs.html"&gt;Requirements for alternate domain names&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Safe Container Updates on a Personal Docker Host</title><link>https://www.yopa.page/blog/2026-08-01-safe-container-updates-on-a-personal-docker-host.html</link><pubDate>Mon, 22 Jan 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-safe-container-updates-on-a-personal-docker-host.html</guid><description>
&lt;p&gt;Automatic container replacement looks like maintenance, but it is also an unattended production deployment. Pulling a mutable &lt;code&gt;latest&lt;/code&gt; tag and restarting several stateful services can introduce a breaking application or database migration with no reviewed change, tested backup, or rollback artifact.&lt;/p&gt;
&lt;p&gt;For a personal Docker or Synology host, the practical goal is not “never automate.” It is to separate &lt;strong&gt;discovery&lt;/strong&gt; from &lt;strong&gt;deployment&lt;/strong&gt; and make each update observable and recoverable.&lt;/p&gt;
&lt;h2 id="the-current-mental-model"&gt;
&lt;a href="#the-current-mental-model" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The current mental model
&lt;/h2&gt;
&lt;p&gt;Use automation to detect new images and notify you. Promote a specific version or digest during a maintenance window after reviewing release notes. Back up persistent data, update one dependency group at a time, verify health and user workflows, then retain the previous image until the rollback window closes.&lt;/p&gt;
&lt;p&gt;Watchtower can still be useful, especially in monitor-only mode. Mounting &lt;code&gt;/var/run/docker.sock&lt;/code&gt; gives a container powerful control over the Docker daemon, effectively a host-level trust boundary. Run only a pinned, trusted Watchtower image; limit the containers it observes with labels or an explicit list; and do not expose its API publicly.&lt;/p&gt;
&lt;h2 id="pin-the-deployment"&gt;
&lt;a href="#pin-the-deployment" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pin the deployment
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;services&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;ghcr.io/example/app:2.4.1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unless-stopped&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;healthcheck&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;test&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="s2"&gt;&amp;#34;CMD&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;wget&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;-q&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--spider&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;http://localhost:8080/health&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;interval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;30s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;5s&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;retries&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;app-data:/var/lib/app&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;watchtower&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;image&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;containrrr/watchtower:1.7.1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;command&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;--&lt;span class="l"&gt;monitor-only --label-enable --notifications-level info&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;environment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;WATCHTOWER_SCHEDULE&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;0 30 8 * * 1&amp;#34;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;- &lt;span class="l"&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;restart&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="l"&gt;unless-stopped&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nt"&gt;volumes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;app-data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Watchtower uses a six-field cron expression that includes seconds. &lt;code&gt;0 30 8 * * 1&lt;/code&gt; means Monday at 08:30. A traditional five-field expression can be rejected or mean something other than intended. Confirm the schedule in the version you actually deploy.&lt;/p&gt;
&lt;p&gt;The example enables monitoring only for containers labeled &lt;code&gt;com.centurylinklabs.watchtower.enable=true&lt;/code&gt;. Notification configuration depends on the chosen provider; test it before relying on it.&lt;/p&gt;
&lt;h2 id="maintenance-window-procedure"&gt;
&lt;a href="#maintenance-window-procedure" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Maintenance-window procedure
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Read application and image release notes, including required intermediate upgrades and schema changes.&lt;/li&gt;
&lt;li&gt;Record the current image reference and Compose configuration.&lt;/li&gt;
&lt;li&gt;Stop application writes if the product requires a consistent backup.&lt;/li&gt;
&lt;li&gt;Back up volumes and external databases, then perform a restore test periodically. A successful archive command is not proof of recoverability.&lt;/li&gt;
&lt;li&gt;Change the image to a specific version or immutable digest and pull it.&lt;/li&gt;
&lt;li&gt;Recreate the affected service without deleting dependencies or volumes.&lt;/li&gt;
&lt;li&gt;Wait for the health check, then test login, reads, writes, scheduled jobs, and dependent services.&lt;/li&gt;
&lt;li&gt;Watch logs and resource use through the rollback window.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;If the new version fails before a one-way data migration, restore the previous image reference. If it migrated data incompatibly, follow the application&amp;rsquo;s documented downgrade path or restore the pre-update backup. Simply starting the old image against a newer schema can make damage worse.&lt;/p&gt;
&lt;h2 id="tradeoffs"&gt;
&lt;a href="#tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Tradeoffs
&lt;/h2&gt;
&lt;p&gt;Full automatic updates reduce patch delay but accept unreviewed breaking changes. Monitor-only automation needs human time but preserves a decision gate. A registry digest is immutable and ideal for reproducibility, while a version tag is more readable but depends on publisher tag discipline. Renovate or Dependabot can propose Compose changes in Git and provide a review trail; that is valuable when the host configuration is managed as code.&lt;/p&gt;
&lt;p&gt;For disposable stateless services, auto-update plus strong health and rollback automation may be reasonable. For databases, media indexes, identity services, or anything with irreplaceable state, require a maintenance gate.&lt;/p&gt;
&lt;p&gt;Do not confuse a container&amp;rsquo;s &lt;code&gt;healthy&lt;/code&gt; status with application acceptance. A health endpoint can succeed while authentication, background jobs, storage permissions, or integrations are broken. Keep a short smoke-test list for the services you actually use. On Synology, also verify Container Manager still points to the intended project, volume paths retain their permissions, and reverse-proxy routes reach the recreated container.&lt;/p&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Replace &lt;code&gt;latest&lt;/code&gt; with a version or digest.&lt;/li&gt;
&lt;li&gt;Move Watchtower to monitor-only before changing deployment policy.&lt;/li&gt;
&lt;li&gt;Correct schedules to six-field syntax and verify timezone.&lt;/li&gt;
&lt;li&gt;Add real health checks and notifications.&lt;/li&gt;
&lt;li&gt;Inventory volumes, databases, configuration, and secrets; document backup and restore.&lt;/li&gt;
&lt;li&gt;Test one service group, dependency ordering, and rollback.&lt;/li&gt;
&lt;li&gt;Remove unused images only after the rollback period.&lt;/li&gt;
&lt;li&gt;Review Docker socket access and keep the management UI private.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Verified on &lt;strong&gt;2026-08-01&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="primary-sources"&gt;
&lt;a href="#primary-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Primary sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://containrrr.dev/watchtower/arguments/"&gt;Watchtower arguments&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://containrrr.dev/watchtower/lifecycle-hooks/"&gt;Watchtower lifecycle hooks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/reference/compose-file/services/#healthcheck"&gt;Docker Compose healthcheck reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/dhi/core-concepts/digests/"&gt;Docker image digests&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Understanding and Managing Narcissistic Behaviors in Everyday Life</title><link>https://www.yopa.page/blog/2024-01-21-understanding-and-managing-narcissistic-behaviors-in-everyday-life.html</link><pubDate>Sun, 21 Jan 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-01-21-understanding-and-managing-narcissistic-behaviors-in-everyday-life.html</guid><description>
&lt;p&gt;&lt;img
src="./images/oni-book.webp"
alt="Image not found: oni-book"
style="
background: #f0f0f0;
color: #666;
padding: 20px;
text-align: center;
border: 1px solid #ddd;
display: block;
"
/&gt;&lt;/p&gt;
&lt;p&gt;In our daily interactions, whether in personal relationships or professional settings, understanding the complexities of human behavior can be crucial. One particularly challenging personality type is the narcissist. Rooted in Wendy T. Behary&amp;rsquo;s insightful book, &lt;a href="https://amzn.to/48GsUMR"&gt;&amp;ldquo;Disarming the Narcissist: Surviving and Thriving with the Self-Absorbed&amp;rdquo;&lt;/a&gt;, this blog post aims to elucidate the characteristics of narcissistic behaviors and offer pragmatic approaches for dealing with individuals who exhibit these traits.&lt;/p&gt;
&lt;h3 id="recognizing-narcissistic-behaviors"&gt;
&lt;a href="#recognizing-narcissistic-behaviors" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Recognizing Narcissistic Behaviors
&lt;/h3&gt;
&lt;p&gt;Narcissistic individuals often display a pattern of traits and behaviors that can be disruptive and challenging to those around them. Key characteristics include:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Grandiosity&lt;/strong&gt;: A pervasive sense of superiority, often accompanied by an exaggeration of talents and achievements.&lt;br&gt;
&lt;em&gt;Example&lt;/em&gt;: Imagine a colleague, John, who frequently boasts about his business acumen and success. He often exaggerates his role in successful projects and claims his strategies are superior to anyone else&amp;rsquo;s in the industry. Despite evidence to the contrary, he maintains an inflated perception of his importance and capabilities.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Need for Admiration&lt;/strong&gt;: A continuous quest for excessive attention and admiration.&lt;br&gt;
&lt;em&gt;Example&lt;/em&gt;: Sarah, a team leader, constantly seeks validation and praise from her peers and subordinates. She requires her team to commend her for every decision she makes, regardless of its impact, and becomes noticeably upset or agitated when her work isn&amp;rsquo;t acknowledged or praised excessively.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Sense of Entitlement&lt;/strong&gt;: Unreasonable expectations of especially favorable treatment and automatic compliance with their expectations.&lt;br&gt;
&lt;em&gt;Example&lt;/em&gt;: Mark expects special treatment at work. He believes he should be exempt from regular duties such as submitting reports on time or attending mandatory meetings. When asked to comply, he reacts indignantly, questioning why he, given his perceived status, should follow the same rules as everyone else.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Exploitative Relationships&lt;/strong&gt;: Taking advantage of others to achieve personal ends without empathy or consideration.&lt;br&gt;
&lt;em&gt;Example&lt;/em&gt;: Lisa often manipulates her colleagues to do her work under the guise of needing help due to her &amp;ldquo;overwhelming&amp;rdquo; workload. She rarely reciprocates or acknowledges their assistance and is quick to take full credit for the collective efforts and successes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lack of Empathy&lt;/strong&gt;: An inability or unwillingness to recognize and honor the feelings and needs of others.&lt;br&gt;
&lt;em&gt;Example&lt;/em&gt;: During a team crisis where everyone is under stress, Alex remains indifferent to his colleagues&amp;rsquo; struggles. He dismisses their concerns and emotions, focusing solely on how the situation benefits or harms his personal agenda, showing little to no regard for the collective morale or well-being.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Envy&lt;/strong&gt;: Feelings of resentment towards others&amp;rsquo; achievements or a belief that others are envious of them.&lt;br&gt;
&lt;em&gt;Example&lt;/em&gt;: Whenever a co-worker receives a compliment or a promotion, Emily reacts with subtle derogatory remarks or attempts to undermine their achievements. She feels resentful when others succeed or are recognized, perceiving it as a direct threat to her status.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Arrogance&lt;/strong&gt;: Displaying snobbish, disdainful, or patronizing attitudes.&lt;br&gt;
&lt;em&gt;Example&lt;/em&gt;: In meetings, Tom often talks down to his peers, dismissing their ideas and input as inferior or irrelevant. He displays a patronizing attitude, believing his insights are the only ones of value and expecting others to conform to his viewpoint without question.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It&amp;rsquo;s worth noting that these traits can be present in anyone and may not necessarily indicate a personality disorder. However, when these behaviors are pervasive, persistent, and negatively impact one&amp;rsquo;s life and relationships, they can be indicative of Narcissistic Personality Disorder (NPD).&lt;/p&gt;
&lt;h3 id="strategies-for-dealing-with-narcissists"&gt;
&lt;a href="#strategies-for-dealing-with-narcissists" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Strategies for Dealing with Narcissists
&lt;/h3&gt;
&lt;p&gt;In &amp;ldquo;Disarming the Narcissist,&amp;rdquo; Behary not only paints a detailed picture of the narcissistic personality but also offers valuable strategies for interacting with individuals exhibiting these traits. Some of these strategies include:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Setting Clear Boundaries&lt;/strong&gt;: Communicate your limits assertively and consistently to prevent boundary overstepping.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Avoiding Power Struggles&lt;/strong&gt;: Engage selectively in conflicts. Focus on maintaining personal peace and professional integrity instead of winning every battle.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Using &amp;lsquo;We&amp;rsquo; Language&lt;/strong&gt;: Frame discussions in an inclusive manner to avoid triggering defensiveness and encourage cooperation.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Acknowledging Their Strengths&lt;/strong&gt;: Recognize and appreciate the narcissist&amp;rsquo;s abilities and achievements to make them more receptive to communication.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keeping Emotions in Check&lt;/strong&gt;: Stay calm and composed, especially when the narcissist tries to provoke emotional reactions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Seeking Support&lt;/strong&gt;: Build a support network for guidance and validation when dealing with challenging dynamics.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Documenting Interactions&lt;/strong&gt;: Keep records of interactions, especially in professional settings, to maintain an objective account of events.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Focusing on Solutions&lt;/strong&gt;: Steer conversations towards constructive outcomes rather than dwelling on problems.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="wrapping-it-up-"&gt;
&lt;a href="#wrapping-it-up-" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Wrapping it up 👏
&lt;/h3&gt;
&lt;p&gt;Navigating relationships with narcissistic individuals can be challenging, but with understanding and the right strategies, it&amp;rsquo;s possible to maintain a sense of balance and peace. The book, &lt;a href="https://amzn.to/48GsUMR"&gt;&amp;ldquo;Disarming the Narcissist: Surviving and Thriving with the Self-Absorbed&amp;rdquo;&lt;/a&gt;, provides not just a lens to understand narcissism but also practical tools to manage and thrive in the presence of narcissistic behaviors. Whether in personal relationships or professional arenas, these insights are invaluable for fostering healthier and more constructive interactions.&lt;/p&gt;
&lt;p&gt;Instead of falling into the role of a victim, arm yourself with these strategies and tame them.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s to all who bravely navigate these turbulent waters! Godspeed.
Cheers. 🍺&lt;/p&gt;</description></item><item><title>Environment Variables in Shell Scripts with set -a</title><link>https://www.yopa.page/blog/2024-01-20-mastering-environment-variables-in-shell-scripts-the-power-of-.env-set-a-and-set-a.html</link><pubDate>Sat, 20 Jan 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-01-20-mastering-environment-variables-in-shell-scripts-the-power-of-.env-set-a-and-set-a.html</guid><description>
&lt;p&gt;&lt;img
src="./images/oni-shell.webp"
alt="Image not found: oni-shell"
style="
background: #f0f0f0;
color: #666;
padding: 20px;
text-align: center;
border: 1px solid #ddd;
display: block;
"
/&gt;&lt;/p&gt;
&lt;p&gt;Managing environment variables effectively is crucial in the realm of shell scripting, especially when dealing with complex projects that rely on external configurations. Traditionally, the &lt;code&gt;source&lt;/code&gt; command is used to import variables from &lt;code&gt;.env&lt;/code&gt; files. However, this article takes you a step further, illustrating the meticulous art of combining &lt;code&gt;.env&lt;/code&gt; files with the &lt;code&gt;set -a&lt;/code&gt; and &lt;code&gt;set +a&lt;/code&gt; shell commands for a more robust and secure approach to environment variable management.&lt;/p&gt;
&lt;h3 id="understanding-the-basics"&gt;
&lt;a href="#understanding-the-basics" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Understanding the Basics
&lt;/h3&gt;
&lt;p&gt;Before diving deep, it&amp;rsquo;s important to grasp the basics. An environment variable is a dynamic-named value that can affect the way running processes will behave on a computer. For instance, they can store information about the default text editor or browser, the path to executable files, or the system locale and language.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.env&lt;/code&gt; files are a simple and popular method to configure environment variables. They are plain text files containing key-value pairs, making them easy to create and manage. However, variables defined in &lt;code&gt;.env&lt;/code&gt; files aren&amp;rsquo;t automatically available in the shell script. They need to be explicitly imported.&lt;/p&gt;
&lt;h3 id="the-role-of-source-env"&gt;
&lt;a href="#the-role-of-source-env" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Role of &lt;code&gt;source .env&lt;/code&gt;
&lt;/h3&gt;
&lt;p&gt;Sourcing a file executes it as shell code; &lt;code&gt;.env&lt;/code&gt; is not a universal data format. Only source a file you control, quote its path, restrict its permissions, and keep secrets in the deployment platform secret store.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;source&lt;/code&gt; command, used as &lt;code&gt;source .env&lt;/code&gt;, executes the file passed as an argument, in the current shell. It reads and imports environment variables from &lt;code&gt;.env&lt;/code&gt; into the shell session, but it doesn&amp;rsquo;t export them to child processes by default.&lt;/p&gt;
&lt;h3 id="elevating-with-set--a-and-set-a"&gt;
&lt;a href="#elevating-with-set--a-and-set-a" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Elevating with &lt;code&gt;set -a&lt;/code&gt; and &lt;code&gt;set +a&lt;/code&gt;
&lt;/h3&gt;
&lt;p&gt;This is where &lt;code&gt;set -a&lt;/code&gt; and &lt;code&gt;set +a&lt;/code&gt; come into play. These commands elevate the way environment variables are handled in shell scripts.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;set -a&lt;/code&gt; (Automatic Export):&lt;/strong&gt; When you run &lt;code&gt;set -a&lt;/code&gt; before sourcing your &lt;code&gt;.env&lt;/code&gt; file, it automatically exports the variables defined after it. This means the variables are not just available to the script but also to any subprocesses or commands that the script executes. It ensures that the environment is consistent and predictable, not just in your script but in other tools or scripts that your script might call.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;&lt;code&gt;set +a&lt;/code&gt; (Revert Automatic Export):&lt;/strong&gt; After importing your variables, &lt;code&gt;set +a&lt;/code&gt; is used to revert the shell back to its default behavior. It&amp;rsquo;s a good practice, preventing any further variables from being automatically exported. This limits the scope of &lt;code&gt;set -a&lt;/code&gt; to only the variables you intend to export, minimizing side effects and enhancing security.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="why-use-set--a-and-set-a"&gt;
&lt;a href="#why-use-set--a-and-set-a" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why Use &lt;code&gt;set -a&lt;/code&gt; and &lt;code&gt;set +a&lt;/code&gt;?
&lt;/h3&gt;
&lt;p&gt;Using these commands provides several benefits:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Explicitness &amp;amp; Clarity:&lt;/strong&gt; Clearly shows the intent to export the variables, making scripts more readable and maintainable.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security:&lt;/strong&gt; By isolating the export behavior to a specific block of code, you reduce the risk of unintentionally exposing variables.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistency &amp;amp; Compatibility:&lt;/strong&gt; Ensures consistent behavior in variable exportation across different environments and shell versions.&lt;/li&gt;
&lt;/ul&gt;
&lt;details&gt;
&lt;summary&gt;&lt;b&gt;Practical Example: Deploying a Web Application&lt;/b&gt;&lt;/summary&gt;
Imagine you're tasked with deploying a web application. This application requires several environment variables to run, such as database connection strings, API keys, and configuration flags. These variables differ between development, staging, and production environments.
&lt;h4 id="steps-in-the-deployment-script"&gt;
&lt;a href="#steps-in-the-deployment-script" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Steps in the Deployment Script
&lt;/h4&gt;
&lt;p&gt;Your deployment script might include steps like:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Setting Up the Environment:&lt;/strong&gt; Load environment variables from a &lt;code&gt;.env&lt;/code&gt; file corresponding to the target environment (development, staging, or production).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Running Database Migrations:&lt;/strong&gt; Execute scripts to set up or update the database schema.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Starting Services:&lt;/strong&gt; Start the application backend, frontend, and any auxiliary services, each of which requires access to the environment variables.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="why-set--a-and-set-a-are-useful-here"&gt;
&lt;a href="#why-set--a-and-set-a-are-useful-here" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why &lt;code&gt;set -a&lt;/code&gt; and &lt;code&gt;set +a&lt;/code&gt; are Useful Here
&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Isolation of Environment Variables:&lt;/strong&gt; You have a &lt;code&gt;.env&lt;/code&gt; file for each environment (&lt;code&gt;.env.development&lt;/code&gt;, &lt;code&gt;.env.staging&lt;/code&gt;, &lt;code&gt;.env.production&lt;/code&gt;). Using &lt;code&gt;set -a&lt;/code&gt; and &lt;code&gt;set +a&lt;/code&gt; ensures that only the variables from the specific &lt;code&gt;.env&lt;/code&gt; file are exported, preventing any accidental overlap or leakage between environments.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ensuring Subprocess Compatibility:&lt;/strong&gt; The database migration scripts, backend, frontend, and auxiliary services are likely separate processes or even separate scripts that are called from your main deployment script. By using &lt;code&gt;set -a&lt;/code&gt;, you ensure that these subprocesses inherit the necessary environment variables, ensuring a smooth and predictable deployment process.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Security and Cleanliness:&lt;/strong&gt; After loading and exporting the necessary variables, &lt;code&gt;set +a&lt;/code&gt; is used to prevent any future variables defined in your script from being automatically exported. This is particularly important for maintaining the security and cleanliness of your environment, as only the necessary variables are exposed to subprocesses.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="script-example"&gt;
&lt;a href="#script-example" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Script Example
&lt;/h4&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/sh
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Choose the right .env file based on the target environment&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;ENV_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;.env.production&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Automatically export variables&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -a
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;. &lt;span class="s2"&gt;&amp;#34;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; +a
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Run database migrations&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./run_database_migrations.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Start services&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./start_backend.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./start_frontend.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;./start_aux_services.sh
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# ... rest of your deployment script&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this script:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;set -a&lt;/code&gt; ensures all variables from &lt;code&gt;.env.production&lt;/code&gt; are exported and available to the &lt;code&gt;run_database_migrations.sh&lt;/code&gt;, &lt;code&gt;start_backend.sh&lt;/code&gt;, &lt;code&gt;start_frontend.sh&lt;/code&gt;, and &lt;code&gt;start_aux_services.sh&lt;/code&gt; scripts.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;set +a&lt;/code&gt; ensures that any variables defined or modified after this point in the script are not automatically exported, maintaining a clean and secure environment.&lt;/li&gt;
&lt;/ul&gt;
&lt;/details&gt;
&lt;h3 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h3&gt;
&lt;p&gt;While &lt;code&gt;source .env&lt;/code&gt; works fine for simple cases, the combination of &lt;code&gt;set -a&lt;/code&gt; and &lt;code&gt;set +a&lt;/code&gt; offers a controlled, secure, and clear approach to managing environment variables in shell scripts. It&amp;rsquo;s a testament to the thoughtfulness that goes into professional script writing, ensuring that your scripts work predictably and securely, no matter the complexity.&lt;/p&gt;
&lt;p&gt;Use &lt;code&gt;set -a&lt;/code&gt; only across the smallest controlled section, restore it with &lt;code&gt;set +a&lt;/code&gt;, and validate every required variable before starting the application.&lt;/p&gt;</description></item><item><title>Run Java Applications on macOS without a System-Wide JDK</title><link>https://www.yopa.page/blog/2024-01-17-run-java-applications-on-macos-without-global-jre-installation-a-guide.html</link><pubDate>Wed, 17 Jan 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-01-17-run-java-applications-on-macos-without-global-jre-installation-a-guide.html</guid><description>
&lt;p&gt;&lt;img
src="./images/oni-docker.webp"
alt="Image not found: oni-docker"
style="
background: #f0f0f0;
color: #666;
padding: 20px;
text-align: center;
border: 1px solid #ddd;
display: block;
"
/&gt;&lt;/p&gt;
&lt;h2 id="introduction"&gt;
&lt;a href="#introduction" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Introduction
&lt;/h2&gt;
&lt;p&gt;Java&amp;rsquo;s omnipresence in software development is unquestionable. Yet, installing Java Runtime Environment (JRE) or Java Development Kit (JDK) globally on macOS may invite version clashes or security risks. This guide delves into alternative strategies for executing Java applications (JAR files) on Mac, sidestepping the global JRE setup. We lay particular emphasis on Docker, a celebrated containerization technology.&lt;/p&gt;
&lt;h2 id="costs-of-a-system-wide-jdk"&gt;
&lt;a href="#costs-of-a-system-wide-jdk" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Costs of a system-wide JDK
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Version Conflicts:&lt;/strong&gt; Diverse Java applications may demand specific Java versions, clashing with a globally installed JRE.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Security Risks:&lt;/strong&gt; A leaner global software footprint reduces potential vulnerabilities.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Enhanced Portability and Isolation:&lt;/strong&gt; Containerization assures consistent application performance across varying systems.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="method-1-application-enclosed-jre"&gt;
&lt;a href="#method-1-application-enclosed-jre" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Method 1: Application-Enclosed JRE
&lt;/h2&gt;
&lt;p&gt;Encapsulate a JRE within your Java application to dodge system-wide Java conflicts.&lt;/p&gt;
&lt;h3 id="steps"&gt;
&lt;a href="#steps" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Steps:
&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Acquire a JRE&lt;/strong&gt; that aligns with your application&amp;rsquo;s and macOS&amp;rsquo;s architecture.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Embed the JRE in your application&amp;rsquo;s folder&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Forge a startup script&lt;/strong&gt; to designate the encapsulated JRE as &lt;code&gt;JAVA_HOME&lt;/code&gt; and initiate your application.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="method-2-a-project-local-runtime"&gt;
&lt;a href="#method-2-a-project-local-runtime" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Method 2: A project-local runtime
&lt;/h2&gt;
&lt;p&gt;A portable JRE facilitates Java application execution sans system-wide Java installation.&lt;/p&gt;
&lt;h3 id="steps-1"&gt;
&lt;a href="#steps-1" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Steps:
&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Procure a portable JRE&lt;/strong&gt; suitable for macOS.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unpack it&lt;/strong&gt; in a directory of your choice.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Execute your JAR file&lt;/strong&gt; using the absolute path to the &lt;code&gt;java&lt;/code&gt; command within the portable JRE&amp;rsquo;s folder.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="method-3-containerized-service-execution"&gt;
&lt;a href="#method-3-containerized-service-execution" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Method 3: Containerized service execution
&lt;/h2&gt;
&lt;p&gt;Docker offers application execution in insulated settings. Here&amp;rsquo;s how to use Docker for your JAR file:&lt;/p&gt;
&lt;h3 id="1-docker-installation"&gt;
&lt;a href="#1-docker-installation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1. Docker Installation
&lt;/h3&gt;
&lt;p&gt;Fetch Docker from the &lt;a href="https://www.docker.com/products/docker-desktop"&gt;official portal&lt;/a&gt; and set it up on your Mac.&lt;/p&gt;
&lt;h3 id="2-java-application-preparation"&gt;
&lt;a href="#2-java-application-preparation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2. Java Application Preparation
&lt;/h3&gt;
&lt;p&gt;Consolidate your JAR file and all necessary assets in a single folder.&lt;/p&gt;
&lt;h3 id="3-crafting-a-dockerfile"&gt;
&lt;a href="#3-crafting-a-dockerfile" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
3. Crafting a Dockerfile
&lt;/h3&gt;
&lt;p&gt;In your project folder, craft a Dockerfile with these specifics:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-dockerfile" data-lang="dockerfile"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Opt for an official Java runtime as the base image&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;eclipse-temurin:25-jre&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Designate the working area in the container&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;/usr/app&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Transport current directory&amp;#39;s contents into /usr/app in the container&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;COPY&lt;/span&gt; . /usr/app&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Render port 8080 accessible externally&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;EXPOSE&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;8080&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c"&gt;# Execute the JAR file&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;java&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;-jar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;your-application.jar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="err"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Tweak the port and JAR file name as needed.&lt;/p&gt;
&lt;h3 id="4-docker-image-construction"&gt;
&lt;a href="#4-docker-image-construction" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
4. Docker Image Construction
&lt;/h3&gt;
&lt;p&gt;In your terminal, within the project folder, execute:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker build -t your-app-name .
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="5-launching-your-application"&gt;
&lt;a href="#5-launching-your-application" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
5. Launching Your Application
&lt;/h3&gt;
&lt;p&gt;Activate your application with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;docker run -p 8080:8080 your-app-name
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Your Java application is now operational within a Docker container!&lt;/p&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;Employing macOS to run JAR files sans a global JRE installation is streamlined with the right toolkit. Whether you opt for a bundled JRE, a portable JRE, or Docker&amp;rsquo;s robust containerization, each avenue offers distinct merits. Containers are useful for services that already have a container deployment target, but they are not the default way to launch a local desktop JAR. For distributable macOS applications, prefer &lt;code&gt;jpackage&lt;/code&gt; with a runtime produced by &lt;code&gt;jlink&lt;/code&gt;; for development, use a version-managed supported JDK. It eliminates the global JRE dependency while assuring an insulated, controlled application environment.&lt;/p&gt;</description></item><item><title>Mastering the Art of Effective Writing in the Information Era</title><link>https://www.yopa.page/blog/2024-01-15-mastering-the-art-of-effective-writing-in-the-information-era.html</link><pubDate>Mon, 15 Jan 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-01-15-mastering-the-art-of-effective-writing-in-the-information-era.html</guid><description>
&lt;p&gt;&lt;img
src="./images/oni-writing.webp"
alt="Image not found: oni-writing"
style="
background: #f0f0f0;
color: #666;
padding: 20px;
text-align: center;
border: 1px solid #ddd;
display: block;
"
/&gt;&lt;/p&gt;
&lt;h2 id="the-power-of-effective-writing-in-the-modern-world"&gt;
&lt;a href="#the-power-of-effective-writing-in-the-modern-world" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Power of Effective Writing in the Modern World
&lt;/h2&gt;
&lt;p&gt;In an age brimming with media and information, the ability to write effectively has become more crucial than ever. The book &lt;strong&gt;“Writing for Busy Readers: Communicate More Effectively in the Real World”&lt;/strong&gt; by Todd Rogers and Jessica Lasky-Fink emerges as a beacon for those seeking to cut through the noise and reach &amp;lsquo;busy&amp;rsquo; readers. This book is not just about writing beautifully; it&amp;rsquo;s about writing effectively, a skill that has become essential in a world where people are overwhelmed with digital messages.&lt;/p&gt;
&lt;h3 id="the-importance-of-clarity-and-brevity"&gt;
&lt;a href="#the-importance-of-clarity-and-brevity" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Importance of Clarity and Brevity
&lt;/h3&gt;
&lt;p&gt;The book emphasizes the power of brevity and clarity. Just as not everyone who sings is a great singer, not everyone who writes does it exceptionally well. Great singers hone their skills through techniques grounded in anatomy, acoustics, and cognition. Similarly, effective writers must study neuroscience and cognitive science to understand how a busy person&amp;rsquo;s brain processes written information. This understanding helps in creating content that captures attention amidst the myriad of distractions vying for a reader&amp;rsquo;s focus.&lt;/p&gt;
&lt;h3 id="six-principles-of-effective-writing"&gt;
&lt;a href="#six-principles-of-effective-writing" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Six Principles of Effective Writing
&lt;/h3&gt;
&lt;p&gt;Drawing on research in behavioral science, the authors distill their insights into six key principles:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Less is More&lt;/strong&gt;: Conciseness is key.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Make it Easy to Read&lt;/strong&gt;: Accessibility in writing is crucial.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Easy Navigation&lt;/strong&gt;: Organize content for easy reference.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Appropriate Formatting&lt;/strong&gt;: Use formatting to aid understanding, but avoid excess.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Explain Why it Matters&lt;/strong&gt;: Make the content relevant and engaging.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Ease of Response&lt;/strong&gt;: Facilitate a straightforward response from the reader.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;These principles are not just theoretical; they are backed by practical examples and real-world applications, making them immediately applicable in various contexts, from emails to business reports.&lt;/p&gt;
&lt;h3 id="beyond-aesthetic-writing"&gt;
&lt;a href="#beyond-aesthetic-writing" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Beyond Aesthetic Writing
&lt;/h3&gt;
&lt;p&gt;The book draws a distinction between expressive, literary writing and effective writing. While the former is an art form, steeped in subjectivity and emotional depth, the latter is a science-based skill aimed at clear and direct communication. This differentiation is crucial in today&amp;rsquo;s digital age, where the goal is often to convey specific information quickly and efficiently to busy individuals.&lt;/p&gt;
&lt;h3 id="the-practical-benefits-of-effective-writing"&gt;
&lt;a href="#the-practical-benefits-of-effective-writing" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Practical Benefits of Effective Writing
&lt;/h3&gt;
&lt;p&gt;Effective writing is not just about getting a message across; it also clarifies the writer&amp;rsquo;s own thoughts. &lt;strong&gt;As the book points out, clear writing leads to clear thinking.&lt;/strong&gt; This clarity is invaluable in our fast-paced world, where misunderstandings can lead to significant time loss and frustration.&lt;/p&gt;
&lt;h3 id="wrapping-it-up-"&gt;
&lt;a href="#wrapping-it-up-" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Wrapping it up 👏
&lt;/h3&gt;
&lt;p&gt;Alright, wrapping things up here: &amp;ldquo;Writing for Busy Readers&amp;rdquo; is pretty much your go-to book if you want to step up your writing game in this crazy-busy world full of info overload. It&amp;rsquo;s not just about sounding smart or fancy; it&amp;rsquo;s more about getting your point across effectively, which, let&amp;rsquo;s face it, is super important these days.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re into getting more details and real-life examples of how to write like a pro, definitely give this book a read. It&amp;rsquo;s packed with useful tips and tricks that&amp;rsquo;ll make a big difference in how you communicate, especially online and in the digital space.&lt;/p&gt;
&lt;p&gt;Feel like checking it out? Here&amp;rsquo;s a link to grab a copy and see for yourself: &lt;a href="https://amzn.to/48LY0lQ"&gt;Writing for Busy Readers: Communicate More Effectively in the Real World&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Cheers! 🍺&lt;/p&gt;</description></item><item><title>Freeing Up Ports 80 and 443 on Synology NAS for Reverse Proxy Usage</title><link>https://www.yopa.page/blog/2024-01-14-freeing-up-ports-80-and-443-on-synology-nas-for-reverse-proxy-usage.html</link><pubDate>Sun, 14 Jan 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-01-14-freeing-up-ports-80-and-443-on-synology-nas-for-reverse-proxy-usage.html</guid><description>
&lt;p&gt;&lt;img
src="./images/red-oni-synology.webp"
alt="Image not found: Working on Synology"
style="
background: #f0f0f0;
color: #666;
padding: 20px;
text-align: center;
border: 1px solid #ddd;
display: block;
"
/&gt;&lt;/p&gt;
&lt;h1 id="freeing-up-ports-80-and-443-on-synology-nas-for-reverse-proxy-usage"&gt;
&lt;a href="#freeing-up-ports-80-and-443-on-synology-nas-for-reverse-proxy-usage" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Freeing Up Ports 80 and 443 on Synology NAS for Reverse Proxy Usage
&lt;/h1&gt;
&lt;h2 id="introduction"&gt;
&lt;a href="#introduction" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Introduction
&lt;/h2&gt;
&lt;p&gt;For Synology NAS users looking to implement reverse proxies such as Traefik, Nginx Proxy Manager, or Caddy, the usage of ports 80 and 443 by default NAS services can be a hurdle. These ports, often occupied by the NAS via Nginx, are essential for reverse proxy operations. Fortunately, a Bash script can efficiently reconfigure Nginx, freeing up these crucial ports.&lt;/p&gt;
&lt;p&gt;In this article, we explore a Bash script solution for modifying the default HTTP and HTTPS ports on Synology NAS, enabling the use of ports 80 and 443 for other applications.&lt;/p&gt;
&lt;h2 id="understanding-the-port-conflict"&gt;
&lt;a href="#understanding-the-port-conflict" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Understanding the Port Conflict
&lt;/h2&gt;
&lt;p&gt;Synology NAS systems employ Nginx, a widespread web server, in their operating framework. Nginx, by default, listens on ports 80 (HTTP) and 443 (HTTPS). This default setting is suitable for regular operations but creates a conflict for reverse proxy servers that require these same ports.&lt;/p&gt;
&lt;h2 id="the-solution-a-bash-script"&gt;
&lt;a href="#the-solution-a-bash-script" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Solution: A Bash Script
&lt;/h2&gt;
&lt;p&gt;A Bash script offers a seamless solution to reconfigure the Nginx server on Synology NAS, reallocating ports 80 and 443 for alternate use. Here’s how the script functions:&lt;/p&gt;
&lt;h3 id="script-breakdown"&gt;
&lt;a href="#script-breakdown" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Script Breakdown
&lt;/h3&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#! /bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# NGINX Ports - CUSTOMIZATION REQUIRED&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;DEFAULT_HTTP_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;80&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;DEFAULT_HTTPS_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;443&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;NEW_HTTP_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;81&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;NEW_HTTPS_PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;444&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Modifying Nginx Configuration&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sed -i &lt;span class="s2"&gt;&amp;#34;s/^\([ \t]\+listen[ \t]\+[]:[]*\)&lt;/span&gt;&lt;span class="nv"&gt;$DEFAULT_HTTP_PORT&lt;/span&gt;&lt;span class="s2"&gt;\([^0-9]\)/\1&lt;/span&gt;&lt;span class="nv"&gt;$NEW_HTTP_PORT&lt;/span&gt;&lt;span class="s2"&gt;\2/&amp;#34;&lt;/span&gt; /usr/syno/share/nginx/*.mustache
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sed -i &lt;span class="s2"&gt;&amp;#34;s/^\([ \t]\+listen[ \t]\+[]:[]*\)&lt;/span&gt;&lt;span class="nv"&gt;$DEFAULT_HTTPS_PORT&lt;/span&gt;&lt;span class="s2"&gt;\([^0-9]\)/\1&lt;/span&gt;&lt;span class="nv"&gt;$NEW_HTTPS_PORT&lt;/span&gt;&lt;span class="s2"&gt;\2/&amp;#34;&lt;/span&gt; /usr/syno/share/nginx/*.mustache
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Restart Nginx to apply changes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;synosystemctl restart nginx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="key-aspects-of-the-script"&gt;
&lt;a href="#key-aspects-of-the-script" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Key Aspects of the Script
&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Customization&lt;/strong&gt;: Modify &lt;code&gt;NEW_HTTP_PORT&lt;/code&gt; and &lt;code&gt;NEW_HTTPS_PORT&lt;/code&gt; variables to designate alternative Nginx ports.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Automated Editing&lt;/strong&gt;: Utilizes &lt;code&gt;sed&lt;/code&gt; for replacing default ports in Nginx configuration files.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Restart Nginx&lt;/strong&gt;: Finalizes with a command to restart Nginx, implementing the adjustments.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="implementation-steps"&gt;
&lt;a href="#implementation-steps" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Implementation Steps
&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Create the Script&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Copy and save the script on your Synology NAS with a &lt;code&gt;.sh&lt;/code&gt; extension and execution permissions.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Schedule the Script&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Set it to run as &lt;code&gt;root&lt;/code&gt; at boot through DSM’s Control Panel -&amp;gt; Task Scheduler.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Run and Verify&lt;/strong&gt;:
&lt;ul&gt;
&lt;li&gt;Manually execute or reboot NAS for testing.&lt;/li&gt;
&lt;li&gt;Confirm port changes with tools like &lt;code&gt;netstat&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="points-to-remember"&gt;
&lt;a href="#points-to-remember" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Points to Remember
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DSM Upgrades&lt;/strong&gt;: Synology&amp;rsquo;s DSM updates may revert these configurations; hence, scheduling the script is vital.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Port Selection&lt;/strong&gt;: Choose unused ports on your NAS for the new settings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Access Control&lt;/strong&gt;: Update firewall rules and port forwarding accordingly.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;This Bash script provides a straightforward and effective way for Synology NAS users to employ reverse proxies by reassigning the default HTTP and HTTPS ports used by Nginx. It&amp;rsquo;s a practical solution for resolving port conflicts and enhancing the functionality of your NAS. When implementing this script, always ensure to follow network security best practices and check for any service conflicts with the new port assignments.&lt;/p&gt;
&lt;p&gt;Adapting and verifying the script based on individual network configurations and Synology NAS models is recommended for optimal results. This approach not only solves the immediate port conflict issue but also opens up new possibilities for using your NAS for diverse applications, from hosting websites to running complex network services.&lt;/p&gt;
&lt;p&gt;Cheers! 🍺&lt;/p&gt;</description></item><item><title>Unboxing Leadership - Insights from "Leadership &amp; Self-Deception"</title><link>https://www.yopa.page/blog/2024-01-13-unboxing-leadership-insights-from-leadership--self-deception.html</link><pubDate>Sat, 13 Jan 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-01-13-unboxing-leadership-insights-from-leadership--self-deception.html</guid><description>
&lt;p&gt;&lt;img
src="./images/oni-box.webp"
alt="Image not found: oni-box"
style="
background: #f0f0f0;
color: #666;
padding: 20px;
text-align: center;
border: 1px solid #ddd;
display: block;
"
/&gt;&lt;/p&gt;
&lt;h2 id="introduction"&gt;
&lt;a href="#introduction" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Introduction
&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;&amp;ldquo;Leadership &amp;amp; Self-Deception: Getting Out of the Box&amp;rdquo;&lt;/strong&gt; by The Arbinger Institute offers transformative guidance in the realms of leadership and personal development, focusing on the impact of self-perception on leadership abilities and interpersonal relations.&lt;/p&gt;
&lt;h2 id="understanding-self-deception"&gt;
&lt;a href="#understanding-self-deception" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Understanding Self-Deception
&lt;/h2&gt;
&lt;p&gt;Self-deception, a core theme of the book, is about having a skewed perception influenced by personal biases and justifications.&lt;/p&gt;
&lt;h3 id="example-of-self-deception"&gt;
&lt;a href="#example-of-self-deception" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Example of Self-Deception
&lt;/h3&gt;
&lt;p&gt;A manager blames his team for missed deadlines, unaware that his poor communication contributes to the issue. This is a classic case of being &amp;lsquo;in the box,&amp;rsquo; seeing only from a self-justifying perspective.&lt;/p&gt;
&lt;h2 id="detailed-exploration-of-self-betrayal"&gt;
&lt;a href="#detailed-exploration-of-self-betrayal" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Detailed Exploration of Self-Betrayal
&lt;/h2&gt;
&lt;p&gt;The book dives deep into the concept of self-betrayal, an act of going against one&amp;rsquo;s inherent sense of right, leading to a distorted view of reality.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Acting Against One&amp;rsquo;s Sense of Right and Wrong:&lt;/strong&gt; An individual ignores their innate sense of right, like a manager not listening to an employee&amp;rsquo;s concern due to busyness.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Justification of Actions:&lt;/strong&gt; The individual justifies their action, like the manager deeming the employee&amp;rsquo;s issue as unimportant.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Distorted View of Others:&lt;/strong&gt; This leads to a skewed perception of others, such as viewing the employee as a complainer.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Creation of a Vicious Cycle:&lt;/strong&gt; This distorted view provokes behaviors that elicit negative responses, reinforcing the distorted perception.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Impact on Relationships and Effectiveness:&lt;/strong&gt; Such cycles damage relationships and reduce effectiveness, hindering genuine communication and collaboration.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Breaking the Cycle:&lt;/strong&gt; One must recognize and act contrary to self-betrayal, like a manager genuinely addressing an employee’s concerns.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="key-takeaways-with-examples"&gt;
&lt;a href="#key-takeaways-with-examples" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Key Takeaways with Examples
&lt;/h2&gt;
&lt;h3 id="being-out-of-the-box"&gt;
&lt;a href="#being-out-of-the-box" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Being &amp;lsquo;Out of the Box&amp;rsquo;
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt; A leader sees her employees as unique individuals, adjusting her management style, which enhances morale and productivity.&lt;/p&gt;
&lt;h3 id="resisting-the-blame-game"&gt;
&lt;a href="#resisting-the-blame-game" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Resisting the Blame Game
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt; When a project fails, a supervisor reflects on his own shortcomings, leading to positive changes in future project management.&lt;/p&gt;
&lt;h3 id="embracing-vulnerability"&gt;
&lt;a href="#embracing-vulnerability" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Embracing Vulnerability
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt; A CEO openly shares her challenges, fostering a collaborative and innovative environment.&lt;/p&gt;
&lt;h3 id="prioritizing-relationships"&gt;
&lt;a href="#prioritizing-relationships" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Prioritizing Relationships
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt; An employee invests in understanding colleagues&amp;rsquo; personal goals, enhancing team cooperation.&lt;/p&gt;
&lt;h3 id="leading-with-influence-not-authority"&gt;
&lt;a href="#leading-with-influence-not-authority" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Leading with Influence, Not Authority
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt; A manager seeks team input and integrates their ideas, increasing commitment and ownership.&lt;/p&gt;
&lt;h3 id="reflecting-and-acting"&gt;
&lt;a href="#reflecting-and-acting" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Reflecting and Acting
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt; A team leader, upon realizing his bias against certain ideas, consciously works to give equal consideration to all suggestions.&lt;/p&gt;
&lt;h3 id="encouraging-open-communication"&gt;
&lt;a href="#encouraging-open-communication" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Encouraging Open Communication
&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;Example:&lt;/strong&gt; Regular open forums in an organization allow for the free expression of concerns, fostering a culture of trust and continuous improvement.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://amzn.to/3Hk0RXh"&gt;Leadership &amp;amp; Self-Deception: Getting Out of the Box&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Cheers! 🍺&lt;/p&gt;</description></item><item><title>Managing Multiple Versions with Pyenv and Virtual Environments</title><link>https://www.yopa.page/blog/2024-01-01-managing-multiple-versions-with-pyenv-and-virtual-environments.html</link><pubDate>Mon, 01 Jan 2024 00:00:00 +0000</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2024-01-01-managing-multiple-versions-with-pyenv-and-virtual-environments.html</guid><description>
&lt;p&gt;&lt;img
src="./images/oni-python-1.webp"
alt="Image not found: oni working on python"
style="
background: #f0f0f0;
color: #666;
padding: 20px;
text-align: center;
border: 1px solid #ddd;
display: block;
"
/&gt;&lt;/p&gt;
&lt;h2 id="managing-multiple-versions-with-pyenv-and-virtual-environments"&gt;
&lt;a href="#managing-multiple-versions-with-pyenv-and-virtual-environments" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Managing Multiple Versions with Pyenv and Virtual Environments
&lt;/h2&gt;
&lt;p&gt;When working on multiple projects with different Python versions, it&amp;rsquo;s crucial to avoid conflicts and ensure that each project uses the correct version and dependencies. This is where virtual environments come in. They are isolated Python environments that let you manage packages and dependencies specific to a project without affecting the global Python installation.&lt;/p&gt;
&lt;h2 id="installing-python-using-homebrew-on-macos"&gt;
&lt;a href="#installing-python-using-homebrew-on-macos" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Installing Python Using Homebrew on macOS
&lt;/h2&gt;
&lt;p&gt;Before setting up virtual environments, ensure you have the desired versions of Python installed:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Install Homebrew&lt;/strong&gt;: Install Homebrew, a package manager for macOS, with the following command:
e.g.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;/bin/bash -c &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Install Python&lt;/strong&gt;: Use Homebrew to install the latest version of Python:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew install python
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="managing-multiple-python-versions-with-pyenv"&gt;
&lt;a href="#managing-multiple-python-versions-with-pyenv" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Managing Multiple Python Versions with pyenv
&lt;/h2&gt;
&lt;p&gt;Select a release that is still supported on the &lt;a href="https://devguide.python.org/versions/"&gt;Python versions page&lt;/a&gt;. The concrete version below is an example; pin the version required by the project and use the same value in CI.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;pyenv&lt;/code&gt; is an indispensable tool for managing multiple Python versions:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Install pyenv&lt;/strong&gt;: Install &lt;code&gt;pyenv&lt;/code&gt; using Homebrew:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;brew install pyenv
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Configure Your Shell&lt;/strong&gt;: Add the following to your shell configuration file (&lt;code&gt;.bashrc&lt;/code&gt;, &lt;code&gt;.zshrc&lt;/code&gt;, etc.) and restart your terminal or source the profile:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;pyenv init --path&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;pyenv init -&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Install Python Versions&lt;/strong&gt;: Install the desired Python versions:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pyenv install 3.13.5
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Set Global Python Version&lt;/strong&gt;: Choose a global Python version with &lt;code&gt;pyenv&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pyenv &lt;span class="nb"&gt;local&lt;/span&gt; 3.13.5
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="creating-and-activating-virtual-environments"&gt;
&lt;a href="#creating-and-activating-virtual-environments" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Creating and Activating Virtual Environments
&lt;/h2&gt;
&lt;p&gt;Virtual environments are created and activated to isolate project dependencies:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Creating a Virtual Environment&lt;/strong&gt;: Navigate to your project directory and create a virtual environment:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 -m venv .venv
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Replace &lt;code&gt;.venv&lt;/code&gt; with your preferred environment name.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Activating the Virtual Environment&lt;/strong&gt;: Activate the virtual environment to use it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Upon activation, your shell prompt might change to indicate the active environment. Now, the environment&amp;rsquo;s Python version and packages are isolated from the rest of your system.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="how-virtual-environments-set-the-correct-python-version-and-environment-variables"&gt;
&lt;a href="#how-virtual-environments-set-the-correct-python-version-and-environment-variables" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
How Virtual Environments Set the Correct Python Version and Environment Variables
&lt;/h2&gt;
&lt;p&gt;When a virtual environment is activated:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Python Version&lt;/strong&gt;: The virtual environment uses the Python version it was created with. Even if you have multiple versions installed on your system, the environment will only have access to and use the specific version it was created with.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Environment Variables&lt;/strong&gt;: The activation script adjusts environment variables like &lt;code&gt;PATH&lt;/code&gt; to ensure that when you invoke &lt;code&gt;python&lt;/code&gt; or &lt;code&gt;pip&lt;/code&gt;, you&amp;rsquo;re using the versions installed in the virtual environment&amp;rsquo;s bin directory. This means any Python scripts you run or packages you install will be confined to the virtual environment.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Isolation&lt;/strong&gt;: This isolation prevents different projects from interfering with each other&amp;rsquo;s dependencies. Each virtual environment operates independently, with its own Python binaries and site packages.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="deactivating-virtual-environments"&gt;
&lt;a href="#deactivating-virtual-environments" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Deactivating Virtual Environments
&lt;/h2&gt;
&lt;p&gt;To stop using a virtual environment and revert to your global Python setup, simply run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;deactivate
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Your shell&amp;rsquo;s prompt will return to normal, and you&amp;rsquo;ll be using the global Python version and packages again.&lt;/p&gt;</description></item><item><title>How to Uninstall Salesforce CLI from a Specific Node.js Version on macOS</title><link>https://www.yopa.page/blog/2023-12-01-how-to-uninstall-salesforce-cli-from-a-specific-node.js-version-on-macos.html</link><pubDate>Fri, 01 Dec 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-12-01-how-to-uninstall-salesforce-cli-from-a-specific-node.js-version-on-macos.html</guid><description>
&lt;h2 id="identify-the-active-installation"&gt;
&lt;a href="#identify-the-active-installation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Identify the active installation
&lt;/h2&gt;
&lt;p&gt;Node version managers give each Node.js version its own global npm directory. Before removing anything, determine whether the active &lt;code&gt;sf&lt;/code&gt; executable came from npm or Salesforce&amp;rsquo;s native installer.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;type&lt;/span&gt; -a sf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;command&lt;/span&gt; -v sf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf version --verbose
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm prefix --global
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm list --global --depth&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; @salesforce/cli sfdx-cli
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;If &lt;code&gt;command -v sf&lt;/code&gt; points outside the reported npm prefix, changing npm packages will not remove that executable.&lt;/p&gt;
&lt;h2 id="remove-only-the-package-owned-by-this-node-version"&gt;
&lt;a href="#remove-only-the-package-owned-by-this-node-version" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Remove only the package owned by this Node version
&lt;/h2&gt;
&lt;p&gt;The supported npm package is &lt;code&gt;@salesforce/cli&lt;/code&gt;; &lt;code&gt;sfdx-cli&lt;/code&gt; is legacy. Switch to the intended Node version, confirm its prefix again, and uninstall the package it owns:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm uninstall --global @salesforce/cli
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# If the listing showed the retired package:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm uninstall --global sfdx-cli
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;hash&lt;/span&gt; -r
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;type&lt;/span&gt; -a sf
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Do not manually delete whichever file &lt;code&gt;which&lt;/code&gt; returns, and do not remove &lt;code&gt;~/.sfdx&lt;/code&gt; or other CLI state directories. Those actions bypass the package owner and can destroy authorization or configuration unrelated to this Node version.&lt;/p&gt;
&lt;p&gt;If another &lt;code&gt;sf&lt;/code&gt; remains in &lt;code&gt;type -a&lt;/code&gt;, identify its owner before deciding whether it should stay. A native installer is a good workstation default because it does not move when &lt;code&gt;nvm&lt;/code&gt; changes Node versions; npm is reasonable when the team deliberately manages global tools per Node version.&lt;/p&gt;
&lt;p&gt;Verify removal in a fresh shell. If you intend to keep Salesforce CLI, install one supported distribution and run &lt;code&gt;sf doctor&lt;/code&gt; plus a read-only command against a nonproduction org. Reviewed on 2026-08-01 against &lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-guide/guide/install-sfdx-cli.html"&gt;Install Salesforce CLI&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Domain-Driven Design Practices and Pitfalls</title><link>https://www.yopa.page/blog/2023-11-30-mastering-domain-driven-design-best-practices-and-pitfalls.html</link><pubDate>Thu, 30 Nov 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-11-30-mastering-domain-driven-design-best-practices-and-pitfalls.html</guid><description>
&lt;h2 id="navigating-the-world-of-ddd-dos-and-donts"&gt;
&lt;a href="#navigating-the-world-of-ddd-dos-and-donts" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Navigating the World of DDD: Do&amp;rsquo;s and Don&amp;rsquo;ts
&lt;/h2&gt;
&lt;p&gt;Domain-Driven Design is most useful when a team applies it to real domain complexity rather than adopting its vocabulary as an architecture template. The following practices keep modeling connected to business behavior.&lt;/p&gt;
&lt;h3 id="ddd-the-dos"&gt;
&lt;a href="#ddd-the-dos" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
DDD: The Do&amp;rsquo;s
&lt;/h3&gt;
&lt;h4 id="1-embrace-ubiquitous-language"&gt;
&lt;a href="#1-embrace-ubiquitous-language" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1. &lt;strong&gt;Embrace Ubiquitous Language&lt;/strong&gt;
&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Do&lt;/strong&gt;: Develop a common language that&amp;rsquo;s understood by all team members, regardless of their technical background. This ensures that everyone is on the same page and reduces misunderstandings.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t&lt;/strong&gt;: Allow jargon or technical terms to dominate discussions, as this can alienate non-technical stakeholders and create barriers.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="2-focus-on-the-domain-model"&gt;
&lt;a href="#2-focus-on-the-domain-model" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2. &lt;strong&gt;Focus on the Domain Model&lt;/strong&gt;
&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Do&lt;/strong&gt;: Invest time in creating a robust and accurate domain model that reflects the business&amp;rsquo;s needs and realities. This model should be the heart of your project.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t&lt;/strong&gt;: Rush the modeling process or overlook the nuances of the business domain. An oversimplified or inaccurate model can lead to flawed software design.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="3-utilize-bounded-contexts"&gt;
&lt;a href="#3-utilize-bounded-contexts" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
3. &lt;strong&gt;Utilize Bounded Contexts&lt;/strong&gt;
&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Do&lt;/strong&gt;: Clearly define and respect the boundaries of each context within your domain. This helps in managing complexity and maintaining focus.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t&lt;/strong&gt;: Ignore the boundaries between contexts or allow them to become blurred, as this can lead to a tangled, hard-to-maintain codebase.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="4-engage-in-continuous-learning"&gt;
&lt;a href="#4-engage-in-continuous-learning" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
4. &lt;strong&gt;Engage in Continuous Learning&lt;/strong&gt;
&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Do&lt;/strong&gt;: Encourage ongoing learning and collaboration among team members. DDD is as much about people and processes as it is about technology.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t&lt;/strong&gt;: Become complacent or stop evolving your understanding of the domain. DDD requires a mindset of continuous improvement.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="ddd-the-donts"&gt;
&lt;a href="#ddd-the-donts" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
DDD: The Don&amp;rsquo;ts
&lt;/h3&gt;
&lt;h4 id="1-avoid-over-engineering"&gt;
&lt;a href="#1-avoid-over-engineering" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1. &lt;strong&gt;Avoid Over-Engineering&lt;/strong&gt;
&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Do&lt;/strong&gt;: Keep solutions as simple and as relevant to the domain as possible.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t&lt;/strong&gt;: Overcomplicate your design with unnecessary patterns or technologies that don&amp;rsquo;t serve the domain&amp;rsquo;s needs.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="2-beware-of-siloed-knowledge"&gt;
&lt;a href="#2-beware-of-siloed-knowledge" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2. &lt;strong&gt;Beware of Siloed Knowledge&lt;/strong&gt;
&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Do&lt;/strong&gt;: Foster a culture of shared knowledge and collaboration.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t&lt;/strong&gt;: Allow knowledge to become siloed within specific individuals or teams. This can create bottlenecks and dependencies.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="3-resist-the-one-size-fits-all-approach"&gt;
&lt;a href="#3-resist-the-one-size-fits-all-approach" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
3. &lt;strong&gt;Resist the One-Size-Fits-All Approach&lt;/strong&gt;
&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Do&lt;/strong&gt;: Tailor your approach to the specific needs and nuances of each project.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t&lt;/strong&gt;: Apply DDD principles rigidly or dogmatically. What works for one project may not work for another.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="4-dont-ignore-non-technical-stakeholders"&gt;
&lt;a href="#4-dont-ignore-non-technical-stakeholders" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
4. &lt;strong&gt;Don’t Ignore Non-Technical Stakeholders&lt;/strong&gt;
&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Do&lt;/strong&gt;: Involve business experts and other non-technical stakeholders actively in the process.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Don&amp;rsquo;t&lt;/strong&gt;: Develop in isolation from the rest of the business. DDD is a collaborative approach that requires input from all sides.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="wrapping-up"&gt;
&lt;a href="#wrapping-up" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Wrapping Up
&lt;/h3&gt;
&lt;p&gt;Domain-Driven Design is a powerful methodology that, when used wisely, can greatly enhance your software development projects. By following these do&amp;rsquo;s and don&amp;rsquo;ts, you can avoid common pitfalls and leverage DDD to its fullest potential.&lt;/p&gt;
&lt;p&gt;DDD is not primarily a code organization scheme. Its value comes from improving the model and shared language used to solve business problems.&lt;/p&gt;
&lt;p&gt;Eric Evans&amp;rsquo;s &lt;a href="https://amzn.to/3SzH7FT"&gt;&amp;ldquo;Domain-Driven Design: Tackling Complexity in the Heart of Software&amp;rdquo;&lt;/a&gt; remains a useful primary reference for teams that need the strategic and tactical concepts in more depth.&lt;/p&gt;</description></item><item><title>Deep Dive into Domain-Driven Design - Simplifying Complex Projects</title><link>https://www.yopa.page/blog/2023-11-29-deep-dive-into-domain-driven-design-simplifying-complex-projects.html</link><pubDate>Wed, 29 Nov 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-11-29-deep-dive-into-domain-driven-design-simplifying-complex-projects.html</guid><description>
&lt;h2 id="introduction"&gt;
&lt;a href="#introduction" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Introduction
&lt;/h2&gt;
&lt;p&gt;Domain-Driven Design (DDD) helps teams model software around a complex business domain. &lt;strong&gt;Domain Storytelling&lt;/strong&gt; is one collaborative technique for learning how people, processes, and systems interact before committing to a model.&lt;/p&gt;
&lt;h3 id="the-prelude-the-need-for-ddd"&gt;
&lt;a href="#the-prelude-the-need-for-ddd" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Prelude: The Need for DDD
&lt;/h3&gt;
&lt;p&gt;A requirements list without the surrounding business rules, language, and constraints can produce a technically correct implementation of the wrong model. DDD addresses that risk through sustained collaboration with domain experts.&lt;/p&gt;
&lt;h3 id="understanding-domain-driven-design"&gt;
&lt;a href="#understanding-domain-driven-design" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Understanding Domain-Driven Design
&lt;/h3&gt;
&lt;p&gt;DDD is not just a set of practices; it&amp;rsquo;s a philosophy that puts the project&amp;rsquo;s domain at the heart of every decision. Here&amp;rsquo;s what it entails:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Ubiquitous Language&lt;/strong&gt;: This is the cornerstone of DDD. It&amp;rsquo;s about creating a common language that&amp;rsquo;s shared among all stakeholders, from developers to business analysts. This language is used to describe all aspects of the project, ensuring clear communication and understanding.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Model-Driven Design&lt;/strong&gt;: In DDD, the focus is on creating a robust domain model that accurately represents the business domain. This model guides the design of the software, ensuring that it aligns with the business needs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Bounded Contexts&lt;/strong&gt;: DDD divides the domain into multiple bounded contexts, each representing a distinct part of the domain with its own model. This helps in managing complexity by isolating different parts of the domain.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Context Mapping&lt;/strong&gt;: Understanding how these bounded contexts interact with each other is crucial. Context mapping helps in identifying and managing these relationships, ensuring that the overall system remains cohesive.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="domain-storytelling"&gt;
&lt;a href="#domain-storytelling" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Domain Storytelling
&lt;/h3&gt;
&lt;p&gt;Domain Storytelling captures concrete work scenarios in a visual, shared form. It supports discovery; it does not replace bounded-context design or validation with the people doing the work.&lt;/p&gt;
&lt;h4 id="crafting-the-story"&gt;
&lt;a href="#crafting-the-story" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Crafting the Story:
&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Inclusive Gatherings&lt;/strong&gt;: Bring together all stakeholders, ensuring everyone&amp;rsquo;s voice is heard.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Narrative Over Diagrams&lt;/strong&gt;: Use simple, relatable stories to describe user interactions, avoiding technical complexities.&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 id="the-art-of-pictographic-language"&gt;
&lt;a href="#the-art-of-pictographic-language" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Art of Pictographic Language:
&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Simple Elements&lt;/strong&gt;: Use symbols like actors, work objects, actions, and sequence numbers to depict the story.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Guiding Principles&lt;/strong&gt;: Avoid repeating actors, focus on scenarios, and consider the scope in terms of granularity and digital versus physical interactions.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="applying-ddd-the-scenario-car-purchase-journey"&gt;
&lt;a href="#applying-ddd-the-scenario-car-purchase-journey" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Applying DDD; The Scenario: Car Purchase Journey
&lt;/h3&gt;
&lt;p&gt;Imagine a scenario where a customer is looking to purchase a car on installments. This process involves several steps and stakeholders, making it an ideal candidate for applying DDD.&lt;/p&gt;
&lt;h4 id="step-1-understanding-the-domain"&gt;
&lt;a href="#step-1-understanding-the-domain" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 1: Understanding the Domain
&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Identify Key Entities&lt;/strong&gt;: Our domain involves entities like Customer, Car, Salesperson, Finance Agreement, etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Develop Ubiquitous Language&lt;/strong&gt;: Establish common terms understood by all stakeholders. For example, &amp;ldquo;Finance Agreement&amp;rdquo; is a term everyone understands the same way.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="step-2-creating-the-domain-model"&gt;
&lt;a href="#step-2-creating-the-domain-model" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 2: Creating the Domain Model
&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Model the Entities and Their Relationships&lt;/strong&gt;: Map out how these entities interact. For example, a Customer chooses a Car, interacts with a Salesperson, and signs a Finance Agreement.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Focus on Business Logic&lt;/strong&gt;: The domain model should capture the logic and rules of the car purchasing process, such as credit checks, payment plans, etc.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="step-3-implementing-bounded-contexts"&gt;
&lt;a href="#step-3-implementing-bounded-contexts" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 3: Implementing Bounded Contexts
&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Define Contexts&lt;/strong&gt;: Break down the domain into manageable contexts, like &amp;ldquo;Sales,&amp;rdquo; &amp;ldquo;Finance,&amp;rdquo; and &amp;ldquo;Inventory.&amp;rdquo;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Context Interactions&lt;/strong&gt;: Understand how these contexts interact. For instance, &amp;ldquo;Sales&amp;rdquo; may initiate a credit check process in &amp;ldquo;Finance.&amp;rdquo;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="step-4-continuous-collaboration-and-refinement"&gt;
&lt;a href="#step-4-continuous-collaboration-and-refinement" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 4: Continuous Collaboration and Refinement
&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Engage Stakeholders&lt;/strong&gt;: Regularly consult with salespeople, finance officers, and customers to refine the model and understand their needs.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Iterate the Model&lt;/strong&gt;: Continuously refine the model based on feedback and new insights.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="step-5-translating-to-code"&gt;
&lt;a href="#step-5-translating-to-code" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 5: Translating to Code
&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Implementing Entities and Relationships&lt;/strong&gt;: Translate the domain model into code, creating classes, and methods that reflect the real-world entities and processes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Building the User Interface&lt;/strong&gt;: Design the UI to align with the domain model, ensuring it&amp;rsquo;s intuitive for users, like having a clear flow from car selection to financing.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id="step-6-leveraging-domain-storytelling"&gt;
&lt;a href="#step-6-leveraging-domain-storytelling" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 6: Leveraging Domain Storytelling
&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Narrate the Customer’s Journey&lt;/strong&gt;: Use Domain Storytelling to depict the customer&amp;rsquo;s experience, from entering the dealership to driving away with the car.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Visualize with Pictographic Language&lt;/strong&gt;: Create diagrams that represent the steps in the process, such as selecting a car, discussing with a salesperson, and signing documents.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="wrapping-up-embracing-ddd-for-better-collaboration"&gt;
&lt;a href="#wrapping-up-embracing-ddd-for-better-collaboration" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Wrapping Up: Embracing DDD for Better Collaboration
&lt;/h3&gt;
&lt;p&gt;DDD, coupled with Domain Storytelling, is not just a method; it&amp;rsquo;s a mindset shift. It brings clarity, efficiency, and a user-centric focus to software development. It&amp;rsquo;s a way to make the development process more enjoyable and inclusive for everyone involved.&lt;/p&gt;
&lt;h4 id="further-exploration"&gt;
&lt;a href="#further-exploration" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Further Exploration
&lt;/h4&gt;
&lt;p&gt;To delve deeper, check out &amp;ldquo;Domain Storytelling: A Collaborative, Visual, and Agile Way to Build Domain-Driven Software&amp;rdquo; and visit &lt;a href="https://domainstorytelling.org/"&gt;domainstorytelling.org&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The companion article covers practical &lt;a href="https://www.yopa.page/blog/2023-11-30-mastering-domain-driven-design-best-practices-and-pitfalls.html"&gt;DDD practices and pitfalls&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Use DDD where domain complexity justifies the modeling investment. A simple data-entry application may not need the full strategic and tactical pattern set.&lt;/p&gt;</description></item><item><title>Solving SSH Connection Issues on macOS - A Guide to Fixing 'Unprotected Private Key File' Error</title><link>https://www.yopa.page/blog/2023-11-28-solving-ssh-connection-issues-on-macos-a-guide-to-fixing-unprotected-private-key-file-error.html</link><pubDate>Tue, 28 Nov 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-11-28-solving-ssh-connection-issues-on-macos-a-guide-to-fixing-unprotected-private-key-file-error.html</guid><description>
&lt;h1 id="solving-ssh-connection-issues-on-macos-a-guide-to-fixing-unprotected-private-key-file-error"&gt;
&lt;a href="#solving-ssh-connection-issues-on-macos-a-guide-to-fixing-unprotected-private-key-file-error" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Solving SSH Connection Issues on macOS: A Guide to Fixing &amp;lsquo;Unprotected Private Key File&amp;rsquo; Error
&lt;/h1&gt;
&lt;p&gt;When working with AWS EC2 instances, particularly from a macOS environment, a common hurdle that many face is the &lt;code&gt;Unprotected Private Key File&lt;/code&gt; error during SSH connections. This error can halt your workflow, preventing access to your remote servers. Understanding and resolving this error is crucial for maintaining a secure and efficient development environment.&lt;/p&gt;
&lt;h2 id="understanding-the-error"&gt;
&lt;a href="#understanding-the-error" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Understanding the Error
&lt;/h2&gt;
&lt;p&gt;The error typically reads:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: UNPROTECTED PRIVATE KEY FILE! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
Permissions 0644 for &amp;#39;ec2-demo.pem&amp;#39; are too open.
It is required that your private key files are NOT accessible by others.
This private key will be ignored.
Load key &amp;#34;ec2-demo.pem&amp;#34;: bad permissions
ec2-user@3.8x.6x.1xx: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This error occurs because the SSH protocol requires that your private key files are kept secure and not accessible by other users on your system. The recommended permissions for these files are &lt;code&gt;600&lt;/code&gt;, allowing only the file&amp;rsquo;s owner to read and write.&lt;/p&gt;
&lt;h2 id="the-solution"&gt;
&lt;a href="#the-solution" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Solution
&lt;/h2&gt;
&lt;h3 id="step-1-open-terminal"&gt;
&lt;a href="#step-1-open-terminal" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 1: Open Terminal
&lt;/h3&gt;
&lt;p&gt;Launch the Terminal application on your macOS. This is where you&amp;rsquo;ll execute commands to modify file permissions.&lt;/p&gt;
&lt;h3 id="step-2-navigate-to-the-key-file"&gt;
&lt;a href="#step-2-navigate-to-the-key-file" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 2: Navigate to the Key File
&lt;/h3&gt;
&lt;p&gt;Use the &lt;code&gt;cd&lt;/code&gt; command to navigate to the directory containing your &lt;code&gt;.pem&lt;/code&gt; file. For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; /path/to/your/key
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Replace &lt;code&gt;/path/to/your/key&lt;/code&gt; with the actual path to your &lt;code&gt;.pem&lt;/code&gt; file.&lt;/p&gt;
&lt;h3 id="step-3-change-file-permissions"&gt;
&lt;a href="#step-3-change-file-permissions" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 3: Change File Permissions
&lt;/h3&gt;
&lt;p&gt;Once in the correct directory, run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;chmod &lt;span class="m"&gt;600&lt;/span&gt; ec2-demo.pem
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This command changes the file permissions to &lt;code&gt;600&lt;/code&gt;, restricting access to only the file&amp;rsquo;s owner.&lt;/p&gt;
&lt;h3 id="step-4-retry-ssh-connection"&gt;
&lt;a href="#step-4-retry-ssh-connection" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 4: Retry SSH Connection
&lt;/h3&gt;
&lt;p&gt;After updating the permissions, connect to your EC2 instance:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ssh -i /path/to/ec2-demo.pem ec2-user@3.8x.6x.1xx
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Replace &lt;code&gt;/path/to/&lt;/code&gt; with the actual path to your &lt;code&gt;.pem&lt;/code&gt; file.&lt;/p&gt;</description></item><item><title>How to Rename a Git Branch Using the CLI</title><link>https://www.yopa.page/blog/2023-10-30-how-to-rename-a-git-branch-using-the-cli.html</link><pubDate>Mon, 30 Oct 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-10-30-how-to-rename-a-git-branch-using-the-cli.html</guid><description>
&lt;h2 id="introduction"&gt;
&lt;a href="#introduction" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Introduction
&lt;/h2&gt;
&lt;p&gt;Renaming a Git branch may seem like a trivial task, but it can become complex, especially when you&amp;rsquo;ve already pushed the branch to a remote repository. In this blog post, we&amp;rsquo;ll go through the steps to rename a local and remote Git branch using the Command-Line Interface (CLI).&lt;/p&gt;
&lt;h2 id="step-1-switch-to-the-branch-you-want-to-rename"&gt;
&lt;a href="#step-1-switch-to-the-branch-you-want-to-rename" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 1: Switch to the Branch You Want to Rename
&lt;/h2&gt;
&lt;p&gt;Before you can rename a branch, you need to switch to it. Open your terminal and run the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git switch old-branch-name
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="step-2-rename-the-local-branch"&gt;
&lt;a href="#step-2-rename-the-local-branch" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 2: Rename the Local Branch
&lt;/h2&gt;
&lt;p&gt;Once you&amp;rsquo;re on the branch you wish to rename, use the following command to rename it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git branch -m new-branch-name
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;-m&lt;/code&gt; flag stands for &amp;ldquo;move,&amp;rdquo; which effectively renames the branch.&lt;/p&gt;
&lt;h2 id="step-3-update-the-remote-branch"&gt;
&lt;a href="#step-3-update-the-remote-branch" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 3: Update the Remote Branch
&lt;/h2&gt;
&lt;p&gt;If you have already pushed the branch to a remote repository and you wish to rename it there as well, you&amp;rsquo;ll need to delete the old branch and then push the new one. Here&amp;rsquo;s how you do it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push origin --delete old-branch-name
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push origin -u new-branch-name
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;-u&lt;/code&gt; flag sets the upstream, so future &lt;code&gt;git pull&lt;/code&gt; and &lt;code&gt;git push&lt;/code&gt; commands on this branch will automatically know which remote branch to interact with.&lt;/p&gt;
&lt;h2 id="step-4-update-any-open-pull-requests"&gt;
&lt;a href="#step-4-update-any-open-pull-requests" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 4: Update Any Open Pull Requests
&lt;/h2&gt;
&lt;p&gt;GitHub does not let you retarget an existing pull request to an arbitrary renamed head branch. Check the pull request state before deleting the old remote branch; you may need to keep it or open a replacement PR.&lt;/p&gt;
&lt;p&gt;If you have any open pull requests that involve the old branch name, remember to update those manually to point to the new branch name.&lt;/p&gt;
&lt;h2 id="step-5-inform-your-team-members"&gt;
&lt;a href="#step-5-inform-your-team-members" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 5: Inform Your Team Members
&lt;/h2&gt;
&lt;p&gt;Renaming a remote branch is a significant change that can affect all team members who are using the same repository. It&amp;rsquo;s good practice to inform everyone about the change. Team members can update their local branches with the following commands:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git branch -m old-branch-name new-branch-name
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git fetch origin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git branch --unset-upstream
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git branch -u origin/new-branch-name
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>How to Clean Up Inaccessible Scratch Org Authorizations</title><link>https://www.yopa.page/blog/2023-09-12-how-to-remove-inaccessible-scratch-orgs-from-your-local-sfdx-configuration.html</link><pubDate>Tue, 12 Sep 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-09-12-how-to-remove-inaccessible-scratch-orgs-from-your-local-sfdx-configuration.html</guid><description>
&lt;h2 id="introduction"&gt;
&lt;a href="#introduction" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Introduction
&lt;/h2&gt;
&lt;p&gt;An expired or already-deleted scratch org can leave a stale local authorization. Use supported CLI commands to diagnose and remove it; don&amp;rsquo;t edit Salesforce CLI&amp;rsquo;s internal files by hand.&lt;/p&gt;
&lt;h2 id="identifying-the-problem"&gt;
&lt;a href="#identifying-the-problem" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Identifying the Problem
&lt;/h2&gt;
&lt;p&gt;Start with &lt;code&gt;sf org list auth&lt;/code&gt; and &lt;code&gt;sf org display --target-org &amp;lt;alias&amp;gt;&lt;/code&gt;. If display fails because the remote org no longer exists, remove only the local authorization.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Symptoms:&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Running &lt;code&gt;sfdx force:org:list&lt;/code&gt; displays the problematic scratch org.
&lt;pre tabindex="0"&gt;&lt;code&gt;force-nforce-003jsL test-ry0x7ndfdivv@example.com 00D040000003jsLEAQ DomainNotFoundError
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;Attempting to delete the org results in an error.
&lt;pre tabindex="0"&gt;&lt;code&gt;ERROR running force:org:delete: The org cannot be found
&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="supported-cleanup-flow"&gt;
&lt;a href="#supported-cleanup-flow" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Supported cleanup flow
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org list auth
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org &lt;span class="nb"&gt;logout&lt;/span&gt; --target-org stale-alias
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;If the scratch org still exists and you intend to dispose of it, use &lt;code&gt;sf org delete scratch --target-org &amp;lt;alias&amp;gt;&lt;/code&gt; instead. This updates the Dev Hub as well as local state.&lt;/p&gt;
&lt;h3 id="reauthenticate-only-when-the-org-still-exists"&gt;
&lt;a href="#reauthenticate-only-when-the-org-still-exists" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Reauthenticate only when the org still exists
&lt;/h3&gt;
&lt;p&gt;If the org still exists on the Salesforce server, sometimes re-authenticating can resolve these issues.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org login web --instance-url https://test.salesforce.com --alias my-scratch
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;After re-authenticating, try to delete the org again.&lt;/p&gt;
&lt;h3 id="move-the-project-default"&gt;
&lt;a href="#move-the-project-default" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Move the project default
&lt;/h3&gt;
&lt;p&gt;Set another default org to potentially resolve some edge cases.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf config &lt;span class="nb"&gt;set&lt;/span&gt; target-org&lt;span class="o"&gt;=&lt;/span&gt;my-new-default
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Do not edit &lt;code&gt;~/.sfdx/orgs.json&lt;/code&gt; or other internal state: storage formats change, and hand edits can corrupt unrelated authorizations. If supported logout fails, run &lt;code&gt;sf doctor&lt;/code&gt;, preserve its sanitized diagnostics, and use Salesforce CLI support guidance. Reviewed against the &lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-reference/guide/cli_reference_org.html"&gt;org command reference&lt;/a&gt; on 2026-08-01.&lt;/p&gt;</description></item><item><title>Safely Update Python Project Dependencies</title><link>https://www.yopa.page/blog/2026-08-01-safe-python-dependency-updates.html</link><pubDate>Fri, 08 Sep 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-safe-python-dependency-updates.html</guid><description>
&lt;p&gt;A Python environment is an installation result, not necessarily the project&amp;rsquo;s dependency policy. Bulk-upgrading every installed distribution mixes direct requirements with transitive packages and can produce a combination nobody reviewed. A safer workflow starts from declared intent, resolves a reproducible result, and proves that the application still works.&lt;/p&gt;
&lt;p&gt;This guide was verified on 2026-08-01 against the &lt;a href="https://packaging.python.org/en/latest/"&gt;Python Packaging User Guide&lt;/a&gt;, pip&amp;rsquo;s &lt;a href="https://pip.pypa.io/en/stable/cli/pip_install/"&gt;&lt;code&gt;pip install&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://pip.pypa.io/en/stable/cli/pip_check/"&gt;&lt;code&gt;pip check&lt;/code&gt;&lt;/a&gt; documentation, and the &lt;a href="https://packaging.python.org/en/latest/specifications/"&gt;PyPA reproducible-environments specifications&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="understand-the-three-layers"&gt;
&lt;a href="#understand-the-three-layers" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Understand the three layers
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Declared dependencies&lt;/strong&gt; describe what the project intentionally needs. They may live in &lt;code&gt;pyproject.toml&lt;/code&gt;, &lt;code&gt;requirements.in&lt;/code&gt;, or another tool&amp;rsquo;s project file.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Resolved dependencies&lt;/strong&gt; select exact compatible versions, often in a lock file or compiled requirements file.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Installed environment&lt;/strong&gt; is one realization of that resolution for a Python version and platform.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;code&gt;pip freeze&lt;/code&gt; reports installed distributions. It cannot reliably tell which packages are direct requirements or why they were selected. That makes it useful for diagnostics and some environment snapshots, but a poor substitute for a maintained project declaration.&lt;/p&gt;
&lt;h2 id="a-safe-update-loop"&gt;
&lt;a href="#a-safe-update-loop" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A safe update loop
&lt;/h2&gt;
&lt;p&gt;Begin in a clean branch and an isolated environment:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 -m venv .venv
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m pip install --upgrade pip
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m pip list --outdated
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Choose a small update scope. Read the direct dependency&amp;rsquo;s release notes, supported Python versions, and migration guidance. Update the declaration using the project&amp;rsquo;s existing tool, regenerate its lock or compiled requirements output, and inspect the diff before installing it.&lt;/p&gt;
&lt;p&gt;For a requirements workflow using pip-tools, that might look like:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m pip install pip-tools
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pip-compile --upgrade-package requests requirements.in
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pip-sync requirements.txt
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;For a standards-based &lt;code&gt;pyproject.toml&lt;/code&gt; project, use the repository&amp;rsquo;s selected resolver or workflow tool. Do not introduce a second lock format merely because another tool is popular. Libraries and deployable applications also have different needs: libraries normally declare compatible ranges, while applications often lock a complete environment for repeatable deployment.&lt;/p&gt;
&lt;h2 id="security-updates-and-automation"&gt;
&lt;a href="#security-updates-and-automation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Security updates and automation
&lt;/h2&gt;
&lt;p&gt;Automated dependency pull requests from Dependabot or Renovate reduce detection time, but they do not remove the need for review. Keep batches small enough to attribute failures. Confirm that package names and download indexes are expected, especially when adding a dependency for the first time.&lt;/p&gt;
&lt;p&gt;A vulnerability report is a prioritization input, not permission to ignore compatibility. Check whether the vulnerable component is reachable in this project, whether a patched compatible version exists, and whether a mitigation is needed while an upgrade is tested. Never paste private lockfiles, index credentials, or internal package names into a public analysis service.&lt;/p&gt;
&lt;h2 id="alternatives-and-tradeoffs"&gt;
&lt;a href="#alternatives-and-tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Alternatives and tradeoffs
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;venv&lt;/code&gt; plus pip is the minimal standard baseline but does not provide a universal lock workflow by itself.&lt;/li&gt;
&lt;li&gt;pip-tools compiles reviewed inputs into pinned requirements and fits many existing pip deployments.&lt;/li&gt;
&lt;li&gt;Poetry, PDM, Hatch, Pipenv, and uv combine different portions of environment, resolution, build, and task management. Choose based on repository requirements and CI support rather than declaring one universal winner.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pipx&lt;/code&gt; is for installing standalone Python applications, not project libraries.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Changing tools is a migration project. Preserve the old declaration until the new resolver produces an accepted environment on every supported platform.&lt;/p&gt;
&lt;h2 id="migration-from-a-frozen-environment"&gt;
&lt;a href="#migration-from-a-frozen-environment" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration from a frozen environment
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Save the current file as evidence; do not immediately treat every line as a direct requirement.&lt;/li&gt;
&lt;li&gt;Identify imports, entry points, runtime services, and explicitly chosen packages.&lt;/li&gt;
&lt;li&gt;Build a minimal direct-dependency declaration with justified version bounds.&lt;/li&gt;
&lt;li&gt;Resolve it in a fresh environment and compare behavior with the old environment.&lt;/li&gt;
&lt;li&gt;Add platform markers and optional dependency groups where they express real differences.&lt;/li&gt;
&lt;li&gt;Adopt one lock or compiled-output convention and document its update command.&lt;/li&gt;
&lt;li&gt;Remove the old snapshot only after CI and deployment validation pass.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="verification-gate"&gt;
&lt;a href="#verification-gate" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Verification gate
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m pip check
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m pytest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Also run the application&amp;rsquo;s normal type checks, linting, build, smoke test, and production-like startup. Recreate the environment from scratch in CI rather than trusting a long-lived local environment. Review the declaration and resolved-file diff together; an unexplained transitive jump is a reason to stop and investigate.&lt;/p&gt;
&lt;p&gt;Record the update date, direct package changed, source release notes, supported Python range, test evidence, and rollback method in the pull request. For services, observe error rate, latency, and startup behavior during a staged rollout. For libraries, test the lowest and highest supported dependency ranges where practical. A successful local import is useful evidence, but it is not a substitute for the consumer-facing compatibility gate.&lt;/p&gt;</description></item><item><title>Salesforce Source Format, Conversion, and Deployment</title><link>https://www.yopa.page/blog/2026-08-01-salesforce-source-format-conversion-and-deployment.html</link><pubDate>Wed, 06 Sep 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-salesforce-source-format-conversion-and-deployment.html</guid><description>
&lt;p&gt;This guide was verified against Salesforce&amp;rsquo;s current CLI reference on 2026-08-01.&lt;/p&gt;
&lt;h2 id="why-the-old-conversion-first-flow-changed"&gt;
&lt;a href="#why-the-old-conversion-first-flow-changed" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why the old conversion-first flow changed
&lt;/h2&gt;
&lt;p&gt;Salesforce DX source format splits large Metadata API files into smaller files that work better in Git. Older workflows often converted that source into Metadata API format before every deployment. Current Salesforce CLI deploys project source directly, including to sandboxes and production orgs. Conversion remains supported, but it is an interoperability tool rather than a required deployment stage.&lt;/p&gt;
&lt;p&gt;The old &lt;code&gt;sfdx force:source:convert&lt;/code&gt; command is also part of the unsupported &lt;code&gt;sfdx&lt;/code&gt; CLI. Keeping it in a build adds an intermediate artifact, extra file-selection logic, and an opportunity for the converted package to drift from the reviewed source.&lt;/p&gt;
&lt;h2 id="current-mental-model"&gt;
&lt;a href="#current-mental-model" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Current mental model
&lt;/h2&gt;
&lt;p&gt;Keep the repository in source format. Use &lt;code&gt;sf project deploy start&lt;/code&gt; for delivery and &lt;code&gt;sf project retrieve start&lt;/code&gt; for deliberate retrieval. Convert only at a boundary where another tool or process explicitly requires Metadata API format.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# See what the CLI intends to deploy.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf project deploy preview --source-dir force-app --target-org staging
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Validate without committing the deployment.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf project deploy start --source-dir force-app --target-org staging &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --dry-run --test-level RunLocalTests
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Deploy the same reviewed scope.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf project deploy start --source-dir force-app --target-org staging &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --test-level RunLocalTests
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Use a manifest when release scope is curated independently of a directory:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf project deploy start --manifest manifest/package.xml &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --target-org staging --dry-run
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Always inspect the preview, identify destructive changes separately, and choose a test level appropriate to the target. Authentication belongs in the workstation keychain or CI secret store, not in the repository.&lt;/p&gt;
&lt;h2 id="when-conversion-is-appropriate"&gt;
&lt;a href="#when-conversion-is-appropriate" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
When conversion is appropriate
&lt;/h2&gt;
&lt;p&gt;Convert source format to Metadata API format when a downstream system accepts a &lt;code&gt;package.xml&lt;/code&gt; bundle and cannot consume a Salesforce DX project:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf project convert &lt;span class="nb"&gt;source&lt;/span&gt; --root-dir force-app &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --output-dir build/mdapi --package-name ReleaseBundle
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Convert a received Metadata API package into source format before maintaining it in Git:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf project convert mdapi --root-dir incoming/unpackaged &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --output-dir force-app
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Conversion is useful for legacy integrations, package inspection, and migrations. Its costs are duplicated artifacts and lossy-looking structural changes: a single object file can become many source files, and recomposition can make diffs hard to reason about. Treat generated output as ephemeral and rebuild it from reviewed source.&lt;/p&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Find &lt;code&gt;sfdx force:source:convert&lt;/code&gt;, &lt;code&gt;mdapi:deploy&lt;/code&gt;, and committed converted directories.&lt;/li&gt;
&lt;li&gt;Confirm whether any downstream consumer truly requires Metadata API format.&lt;/li&gt;
&lt;li&gt;Replace ordinary delivery with &lt;code&gt;sf project deploy preview&lt;/code&gt; and &lt;code&gt;sf project deploy start --dry-run&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Preserve manifests when they express intentional release scope.&lt;/li&gt;
&lt;li&gt;If conversion remains necessary, write to a clean build directory and never edit both representations.&lt;/li&gt;
&lt;li&gt;Test retrieval and deployment in a disposable org, including decomposed metadata such as Custom Objects.&lt;/li&gt;
&lt;li&gt;Run the project test suite and compare the deployed component set before changing production automation.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="verification"&gt;
&lt;a href="#verification" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Verification
&lt;/h2&gt;
&lt;p&gt;A successful command is not enough. Confirm that preview selected the expected components, validation ran the intended tests, the target org is explicit, and Git contains only the source representation. In CI, pin the CLI version and archive deployment reports rather than converted secrets or auth material.&lt;/p&gt;
&lt;h2 id="official-sources"&gt;
&lt;a href="#official-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Official sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-reference/guide/cli_reference.html"&gt;Salesforce CLI command reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-plugin/guide/migrate-sfdx-sf.html"&gt;Migrate from sfdx to sf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_ws_config.htm"&gt;Salesforce DX project structure&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Initializing a Python Project in Visual Studio Code</title><link>https://www.yopa.page/blog/2023-08-31-initializing-a-python-project-in-visual-studio-code.html</link><pubDate>Thu, 31 Aug 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-08-31-initializing-a-python-project-in-visual-studio-code.html</guid><description>
&lt;h2 id="current-vs-code-environment-workflow"&gt;
&lt;a href="#current-vs-code-environment-workflow" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Current VS Code environment workflow
&lt;/h2&gt;
&lt;p&gt;VS Code now provides a Python Environments workflow that discovers &lt;code&gt;.venv&lt;/code&gt;, pyenv, conda, Poetry, Pipenv, and uv environments. Prefer &lt;code&gt;.venv&lt;/code&gt;, &lt;code&gt;python -m pip&lt;/code&gt;, a declared project dependency file, and the repository default branch instead of assuming a global interpreter or &lt;code&gt;master&lt;/code&gt;.&lt;/p&gt;
&lt;h1 id="initializing-a-python-project-in-visual-studio-code"&gt;
&lt;a href="#initializing-a-python-project-in-visual-studio-code" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Initializing a Python Project in Visual Studio Code
&lt;/h1&gt;
&lt;p&gt;Creating a Python project involves more than just writing code. It entails setting up an organized file structure, managing dependencies, and even integrating version control for collaborative development. In this article, we&amp;rsquo;ll go through the steps to initialize a Python project in VS Code, install the necessary dependencies, and set up a GitHub repository for version control.&lt;/p&gt;
&lt;h2 id="step-1-create-a-new-folder-in-vs-code"&gt;
&lt;a href="#step-1-create-a-new-folder-in-vs-code" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 1: Create a New Folder in VS Code
&lt;/h2&gt;
&lt;p&gt;Open Visual Studio Code and create a new folder for your Python project using the &amp;ldquo;New Folder&amp;rdquo; button. This folder will be the root directory of your Python project.&lt;/p&gt;
&lt;h2 id="step-2-initialize-a-virtual-environment"&gt;
&lt;a href="#step-2-initialize-a-virtual-environment" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 2: Initialize a Virtual Environment
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Open the terminal in VS Code by navigating to &lt;code&gt;View&lt;/code&gt; &amp;gt; &lt;code&gt;Terminal&lt;/code&gt; or pressing &lt;code&gt;Ctrl+~&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Run the following command to create a virtual environment. Replace &lt;code&gt;myenv&lt;/code&gt; with your desired name.
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m venv myenv
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="step-3-activate-the-virtual-environment"&gt;
&lt;a href="#step-3-activate-the-virtual-environment" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 3: Activate the Virtual Environment
&lt;/h2&gt;
&lt;p&gt;Activate your virtual environment by running:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;On Windows:
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.&lt;span class="se"&gt;\m&lt;/span&gt;yenv&lt;span class="se"&gt;\S&lt;/span&gt;cripts&lt;span class="se"&gt;\A&lt;/span&gt;ctivate
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;On macOS and Linux:
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;source&lt;/span&gt; myenv/bin/activate
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="step-4-install-required-packages"&gt;
&lt;a href="#step-4-install-required-packages" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 4: Install Required Packages
&lt;/h2&gt;
&lt;p&gt;Install the required Python packages by running the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pip install requests google_images_search
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="step-5-create-python-files"&gt;
&lt;a href="#step-5-create-python-files" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 5: Create Python Files
&lt;/h2&gt;
&lt;p&gt;Create a new Python file (&lt;code&gt;main.py&lt;/code&gt;, for example) and paste your Python code into it.&lt;/p&gt;
&lt;h2 id="initialize-github-repository"&gt;
&lt;a href="#initialize-github-repository" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Initialize GitHub Repository
&lt;/h2&gt;
&lt;h3 id="step-1-initialize-local-repository"&gt;
&lt;a href="#step-1-initialize-local-repository" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 1: Initialize Local Repository
&lt;/h3&gt;
&lt;p&gt;In the terminal, run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git init
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="step-2-create-gitignore-file"&gt;
&lt;a href="#step-2-create-gitignore-file" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 2: Create &lt;code&gt;.gitignore&lt;/code&gt; File
&lt;/h3&gt;
&lt;p&gt;Create a &lt;code&gt;.gitignore&lt;/code&gt; file and add the following:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;myenv/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;__pycache__/
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;*.pyc
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;*.pyo
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="step-3-add-and-commit-files"&gt;
&lt;a href="#step-3-add-and-commit-files" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 3: Add and Commit Files
&lt;/h3&gt;
&lt;p&gt;Run the following commands to add and commit your files locally:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git add .
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;Initial commit&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="step-4-create-a-github-repository"&gt;
&lt;a href="#step-4-create-a-github-repository" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 4: Create a GitHub Repository
&lt;/h3&gt;
&lt;p&gt;Log in to your GitHub account and create a new repository.&lt;/p&gt;
&lt;h3 id="step-5-link-local-repository-to-github"&gt;
&lt;a href="#step-5-link-local-repository-to-github" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 5: Link Local Repository to GitHub
&lt;/h3&gt;
&lt;p&gt;Replace &lt;code&gt;your-repo-url&lt;/code&gt; with the URL of your GitHub repository and run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git remote add origin your-repo-url
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="step-6-push-to-github"&gt;
&lt;a href="#step-6-push-to-github" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 6: Push to GitHub
&lt;/h3&gt;
&lt;p&gt;Push your local commits to the GitHub repository:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push -u origin master
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>Passing Command-Line Arguments to a TypeScript Script using NPM</title><link>https://www.yopa.page/blog/2023-08-18-passing-command-line-arguments-to-a-typescript-script-using-npm.html</link><pubDate>Fri, 18 Aug 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-08-18-passing-command-line-arguments-to-a-typescript-script-using-npm.html</guid><description>
&lt;h2 id="argument-boundary-and-validation"&gt;
&lt;a href="#argument-boundary-and-validation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Argument boundary and validation
&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;npm run script -- arg1 arg2&lt;/code&gt; remains the portable argument boundary. Use the project-pinned TypeScript runner or compiled JavaScript, validate &lt;code&gt;process.argv&lt;/code&gt; with a maintained parser for nontrivial CLIs, and return a nonzero exit code for invalid input.&lt;/p&gt;
&lt;h2 id="introduction"&gt;
&lt;a href="#introduction" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Introduction
&lt;/h2&gt;
&lt;p&gt;In modern JavaScript and TypeScript development, &lt;code&gt;npm&lt;/code&gt; scripts are commonly used to streamline and simplify various tasks. However, there may be occasions when you need to pass arguments to these scripts to make them more dynamic. In this article, we&amp;rsquo;ll explore how to pass command-line arguments to a TypeScript script using &lt;code&gt;npm run&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="setting-up-npm-to-run-your-typescript-script"&gt;
&lt;a href="#setting-up-npm-to-run-your-typescript-script" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Setting up NPM to Run Your TypeScript Script
&lt;/h2&gt;
&lt;p&gt;To run your TypeScript script using the &lt;code&gt;npm run&lt;/code&gt; command, you can define a script in your &lt;code&gt;package.json&lt;/code&gt; file. Here&amp;rsquo;s a step-by-step guide to help you set it up:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Ensure you have the necessary dependencies. Before running TypeScript files, make sure you have the TypeScript compiler and, optionally, &lt;code&gt;ts-node&lt;/code&gt; installed.&lt;/li&gt;
&lt;li&gt;Create or update a &lt;code&gt;tsconfig.json&lt;/code&gt; file to include compiler options for TypeScript.&lt;/li&gt;
&lt;li&gt;Add a script entry to the &lt;code&gt;scripts&lt;/code&gt; section of your &lt;code&gt;package.json&lt;/code&gt; to execute the TypeScript file with &lt;code&gt;ts-node&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="passing-parameters-with-npm-run"&gt;
&lt;a href="#passing-parameters-with-npm-run" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Passing Parameters with NPM Run
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Handling Arguments in TypeScript&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;In your TypeScript script, Node.js provides a global object called &lt;code&gt;process.argv&lt;/code&gt; which is an array containing the command-line arguments passed when the process was launched. The first two values are the node executable and your script&amp;rsquo;s path, so you&amp;rsquo;ll usually want to access the arguments starting from index 2:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;argv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;For our use case, we want to retrieve the &lt;code&gt;arg&lt;/code&gt; argument:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profileArg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--arg=&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profileName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;profileArg&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;profileArg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;split&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;=&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;default&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Updating the NPM Script&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;Your &lt;code&gt;package.json&lt;/code&gt; file remains unchanged. It can look like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-json" data-lang="json"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;&amp;#34;scripts&amp;#34;&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nt"&gt;&amp;#34;start&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;ts-node your-script-file.ts&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Executing the Script with Arguments&lt;/strong&gt;:&lt;/p&gt;
&lt;p&gt;When running your script, use the following format to pass in the &lt;code&gt;arg&lt;/code&gt; argument:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;npm run start -- --arg=myProfile
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The &lt;code&gt;--&lt;/code&gt; after &lt;code&gt;npm run start&lt;/code&gt; indicates that the subsequent arguments should be passed directly to the invoked script.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="advanced-using-argument-parsers"&gt;
&lt;a href="#advanced-using-argument-parsers" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Advanced: Using Argument Parsers
&lt;/h2&gt;
&lt;p&gt;For more advanced command-line argument parsing, consider using libraries like &lt;code&gt;yargs&lt;/code&gt; or &lt;code&gt;commander&lt;/code&gt;. They offer a plethora of features such as default values, required arguments, aliases, and much more, which can make your scripts more robust and user-friendly.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;Passing command-line arguments to your TypeScript scripts via &lt;code&gt;npm run&lt;/code&gt; can greatly enhance their flexibility. Whether you&amp;rsquo;re looking to provide different configurations, paths, or any other kind of data, this method can simplify and streamline your development process.&lt;/p&gt;</description></item><item><title>Using Git Stash to Save and Restore Work in Progress</title><link>https://www.yopa.page/blog/2023-07-31-mastering-git-stash-a-guide-to-saving-and-restoring-your-code-changes.html</link><pubDate>Mon, 31 Jul 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-07-31-mastering-git-stash-a-guide-to-saving-and-restoring-your-code-changes.html</guid><description>
&lt;h2 id="introduction"&gt;
&lt;a href="#introduction" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Introduction
&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;git stash&lt;/code&gt; is one of those Git commands that can be an absolute lifesaver when you&amp;rsquo;re in the middle of a coding session. Imagine you&amp;rsquo;re working on a new feature and suddenly need to switch context—perhaps to a different branch to work on a hotfix. You don&amp;rsquo;t want to commit half-baked code, but you need to save your changes somewhere. Enter &lt;code&gt;git stash&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;In this article, we&amp;rsquo;ll explore various ways to use &lt;code&gt;git stash&lt;/code&gt; to your advantage, helping you to save, manage, and restore your code changes effectively.&lt;/p&gt;
&lt;h2 id="basic-stashing"&gt;
&lt;a href="#basic-stashing" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Basic Stashing
&lt;/h2&gt;
&lt;h3 id="saving-changes"&gt;
&lt;a href="#saving-changes" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Saving Changes
&lt;/h3&gt;
&lt;p&gt;The basic form of the &lt;code&gt;git stash&lt;/code&gt; command is straightforward. Simply run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git stash
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This command takes your modified tracked files and stages them along with any new files you&amp;rsquo;ve indicated with &lt;code&gt;git add&lt;/code&gt;, and stashes them away for later use.&lt;/p&gt;
&lt;h3 id="listing-stashes"&gt;
&lt;a href="#listing-stashes" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Listing Stashes
&lt;/h3&gt;
&lt;p&gt;You can view all your stashes using:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git stash list
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This command will show you a list of all the stashes that you&amp;rsquo;ve created, making it easier to manage them.&lt;/p&gt;
&lt;h2 id="advanced-stashing-techniques"&gt;
&lt;a href="#advanced-stashing-techniques" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Advanced Stashing Techniques
&lt;/h2&gt;
&lt;h3 id="applying-the-latest-stash"&gt;
&lt;a href="#applying-the-latest-stash" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Applying the Latest Stash
&lt;/h3&gt;
&lt;p&gt;You can apply the changes from the most recent stash using:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git stash apply
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This command will keep the stash, allowing you to apply the same changes to multiple branches.&lt;/p&gt;
&lt;h3 id="popping-the-latest-stash"&gt;
&lt;a href="#popping-the-latest-stash" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Popping the Latest Stash
&lt;/h3&gt;
&lt;p&gt;Alternatively, you can use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git stash pop
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This will apply the stashed changes and remove them from your stash list.&lt;/p&gt;
&lt;h3 id="dropping-a-stash"&gt;
&lt;a href="#dropping-a-stash" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Dropping a Stash
&lt;/h3&gt;
&lt;p&gt;If you want to discard a stash, you can use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git stash drop stash@&lt;span class="o"&gt;{&lt;/span&gt;0&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This command will remove the specified stash from your stash list.&lt;/p&gt;
&lt;h3 id="applying-a-specific-stash"&gt;
&lt;a href="#applying-a-specific-stash" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Applying a Specific Stash
&lt;/h3&gt;
&lt;p&gt;If you&amp;rsquo;ve created multiple stashes, you can specify which stash to apply:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git stash apply stash@&lt;span class="o"&gt;{&lt;/span&gt;1&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Or, if you wish to apply and remove it from the stash list:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git stash pop stash@&lt;span class="o"&gt;{&lt;/span&gt;1&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="handling-conflicts"&gt;
&lt;a href="#handling-conflicts" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Handling Conflicts
&lt;/h2&gt;
&lt;p&gt;Just like with merges or rebases, conflicts can occur when you apply or pop a stash. You&amp;rsquo;ll have to resolve these conflicts manually before you can continue your work.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;git stash&lt;/code&gt; temporarily records tracked changes when a short-lived clean working tree is useful. It is not a durable substitute for a branch or commit. Inspect the stash before applying it and retain it until any conflicts are resolved.&lt;/p&gt;
&lt;p&gt;So the next time you find yourself needing to switch tasks but don&amp;rsquo;t want to lose your changes, remember: just stash it!&lt;/p&gt;</description></item><item><title>Checking Object Type in TypeScript</title><link>https://www.yopa.page/blog/2023-07-05-checking-object-type-in-typescript.html</link><pubDate>Wed, 05 Jul 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-07-05-checking-object-type-in-typescript.html</guid><description>
&lt;p&gt;When working with TypeScript, it&amp;rsquo;s often necessary to determine the type of a variable. In particular, you might need to check whether a variable is of object type. Fortunately, TypeScript provides a straightforward way to perform this type check.&lt;/p&gt;
&lt;h2 id="the-typeof-operator"&gt;
&lt;a href="#the-typeof-operator" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The &lt;code&gt;typeof&lt;/code&gt; Operator
&lt;/h2&gt;
&lt;p&gt;In TypeScript, you can use the &lt;code&gt;typeof&lt;/code&gt; operator to check the type of a variable. To determine if a variable is of object type, combine &lt;code&gt;typeof&lt;/code&gt; with the &lt;code&gt;object&lt;/code&gt; keyword.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variable&lt;/span&gt;: &lt;span class="kt"&gt;any&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt; &lt;span class="c1"&gt;// Your variable
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;object&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;The variable is an object.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;The variable is not an object.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this code snippet, the &lt;code&gt;typeof variable === 'object'&lt;/code&gt; condition checks whether the type of &lt;code&gt;variable&lt;/code&gt; is an object. Additionally, &lt;code&gt;variable !== null&lt;/code&gt; ensures that &lt;code&gt;null&lt;/code&gt; is not considered an object.&lt;/p&gt;
&lt;h2 id="specifying-a-specific-object-type"&gt;
&lt;a href="#specifying-a-specific-object-type" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Specifying a Specific Object Type
&lt;/h2&gt;
&lt;p&gt;If you&amp;rsquo;re looking to check for a specific object type, you can replace the &lt;code&gt;any&lt;/code&gt; type in the variable declaration with the desired type.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;variable&lt;/span&gt;: &lt;span class="kt"&gt;SomeObjectType&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{};&lt;/span&gt; &lt;span class="c1"&gt;// Your variable of type SomeObjectType
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;object&amp;#39;&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nx"&gt;variable&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;The variable is an object of type SomeObjectType.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;The variable is not an object of type SomeObjectType.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Replace &lt;code&gt;SomeObjectType&lt;/code&gt; with the actual type you&amp;rsquo;re expecting for your variable.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;Checking the object type in TypeScript is essential when you want to ensure the correctness of your code. By using the &lt;code&gt;typeof&lt;/code&gt; operator in combination with the &lt;code&gt;object&lt;/code&gt; keyword, you can easily determine whether a variable is an object. If you&amp;rsquo;re looking for a specific object type, replace the &lt;code&gt;any&lt;/code&gt; type with the desired type in the variable declaration.&lt;/p&gt;</description></item><item><title>Creating Zip Files from JSON Configuration with Node.js and TypeScript</title><link>https://www.yopa.page/blog/2023-06-28-creating-zip-files-from-json-configuration-with-nodejs-and-typescript.html</link><pubDate>Mon, 03 Jul 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-06-28-creating-zip-files-from-json-configuration-with-nodejs-and-typescript.html</guid><description>
&lt;h2 id="trust-boundaries-and-archive-paths"&gt;
&lt;a href="#trust-boundaries-and-archive-paths" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Trust boundaries and archive paths
&lt;/h2&gt;
&lt;p&gt;do not use this example with untrusted JSON. Constrain every resolved input to an approved root, reject missing files and symbolic-link escapes, generate relative archive entry names, use a unique output path, and await both archive and output-stream failures. The original synchronous sample is suitable only for small, trusted local input after those controls are added.&lt;/p&gt;
&lt;p&gt;In this article, we&amp;rsquo;ll explore a practical use case involving JSON parsing and file operations in Node.js and TypeScript. Specifically, we&amp;rsquo;ll write a script to read a JSON configuration file, extract files and directories specified therein, and zip them into a single file.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;
&lt;a href="#prerequisites" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Prerequisites
&lt;/h2&gt;
&lt;p&gt;Before we get started, make sure you have Node.js and TypeScript installed on your system. Also, you will need the &lt;code&gt;fs&lt;/code&gt;, &lt;code&gt;path&lt;/code&gt; and &lt;code&gt;archiver&lt;/code&gt; libraries. If you haven&amp;rsquo;t installed the &lt;code&gt;archiver&lt;/code&gt; library yet, you can add it to your project using npm:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-shell" data-lang="shell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm install archiver
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="the-code"&gt;
&lt;a href="#the-code" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Code
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s look at the TypeScript script.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;span class="lnt"&gt;42
&lt;/span&gt;&lt;span class="lnt"&gt;43
&lt;/span&gt;&lt;span class="lnt"&gt;44
&lt;/span&gt;&lt;span class="lnt"&gt;45
&lt;/span&gt;&lt;span class="lnt"&gt;46
&lt;/span&gt;&lt;span class="lnt"&gt;47
&lt;/span&gt;&lt;span class="lnt"&gt;48
&lt;/span&gt;&lt;span class="lnt"&gt;49
&lt;/span&gt;&lt;span class="lnt"&gt;50
&lt;/span&gt;&lt;span class="lnt"&gt;51
&lt;/span&gt;&lt;span class="lnt"&gt;52
&lt;/span&gt;&lt;span class="lnt"&gt;53
&lt;/span&gt;&lt;span class="lnt"&gt;54
&lt;/span&gt;&lt;span class="lnt"&gt;55
&lt;/span&gt;&lt;span class="lnt"&gt;56
&lt;/span&gt;&lt;span class="lnt"&gt;57
&lt;/span&gt;&lt;span class="lnt"&gt;58
&lt;/span&gt;&lt;span class="lnt"&gt;59
&lt;/span&gt;&lt;span class="lnt"&gt;60
&lt;/span&gt;&lt;span class="lnt"&gt;61
&lt;/span&gt;&lt;span class="lnt"&gt;62
&lt;/span&gt;&lt;span class="lnt"&gt;63
&lt;/span&gt;&lt;span class="lnt"&gt;64
&lt;/span&gt;&lt;span class="lnt"&gt;65
&lt;/span&gt;&lt;span class="lnt"&gt;66
&lt;/span&gt;&lt;span class="lnt"&gt;67
&lt;/span&gt;&lt;span class="lnt"&gt;68
&lt;/span&gt;&lt;span class="lnt"&gt;69
&lt;/span&gt;&lt;span class="lnt"&gt;70
&lt;/span&gt;&lt;span class="lnt"&gt;71
&lt;/span&gt;&lt;span class="lnt"&gt;72
&lt;/span&gt;&lt;span class="lnt"&gt;73
&lt;/span&gt;&lt;span class="lnt"&gt;74
&lt;/span&gt;&lt;span class="lnt"&gt;75
&lt;/span&gt;&lt;span class="lnt"&gt;76
&lt;/span&gt;&lt;span class="lnt"&gt;77
&lt;/span&gt;&lt;span class="lnt"&gt;78
&lt;/span&gt;&lt;span class="lnt"&gt;79
&lt;/span&gt;&lt;span class="lnt"&gt;80
&lt;/span&gt;&lt;span class="lnt"&gt;81
&lt;/span&gt;&lt;span class="lnt"&gt;82
&lt;/span&gt;&lt;span class="lnt"&gt;83
&lt;/span&gt;&lt;span class="lnt"&gt;84
&lt;/span&gt;&lt;span class="lnt"&gt;85
&lt;/span&gt;&lt;span class="lnt"&gt;86
&lt;/span&gt;&lt;span class="lnt"&gt;87
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;fs&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;path&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;archiver&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;archiver&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getAllFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dirPath&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arrayOfFiles&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readdirSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dirPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;forEach&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dirPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lstatSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isSymbolicLink&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Symbolic links are not allowed: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isDirectory&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;arrayOfFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getAllFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arrayOfFiles&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;arrayOfFiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;candidate&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;arrayOfFiles&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;resolveInside&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;resolved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;relative&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;resolved&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;startsWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;..&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isAbsolute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;relative&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Path escapes the approved root: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;resolved&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;main() {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;approvedRoot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;input-files&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;input.json&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jsonData&lt;/span&gt;: &lt;span class="kt"&gt;unknown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rawData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;jsonData&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;object&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;jsonData&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;configuration&amp;#34;&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="nx"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configuration&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;object&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configuration&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Invalid configuration object&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createWriteStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;output.zip&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;archive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;archiver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;zip&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;zlib&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;level&lt;/span&gt;: &lt;span class="kt"&gt;9&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Sets the compression level.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;close&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;archive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39; total bytes&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;archive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;error&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;: &lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;archive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;values&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;configuration&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;every&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;string&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Every configuration value must be an array of paths&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;configuredPath&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileOrFolder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;resolveInside&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;approvedRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;configuredPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lstatSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileOrFolder&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isSymbolicLink&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Symbolic links are not allowed: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;configuredPath&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;isDirectory&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isDirectory&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;isDirectory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;getAllFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;approvedRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fileOrFolder&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;archive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createReadStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;: &lt;span class="kt"&gt;path.relative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;approvedRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;filePath&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;archive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createReadStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileOrFolder&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;: &lt;span class="kt"&gt;path.relative&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;approvedRoot&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fileOrFolder&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;archive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;finalize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;main&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="how-it-works"&gt;
&lt;a href="#how-it-works" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
How it works
&lt;/h2&gt;
&lt;p&gt;The script parses JSON as &lt;code&gt;unknown&lt;/code&gt; and validates the expected object and path-array shape before using it. Each configured path is resolved relative to &lt;code&gt;input-files&lt;/code&gt;; &lt;code&gt;resolveInside&lt;/code&gt; rejects traversal outside that root.&lt;/p&gt;
&lt;p&gt;It then creates a write stream for the output zip file and sets up the archiver to zip files.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;output&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createWriteStream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;output.zip&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;archive&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;archiver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;zip&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;zlib&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;level&lt;/span&gt;: &lt;span class="kt"&gt;9&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// Sets the compression level.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;close&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;archive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pointer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39; total bytes&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;archive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;on&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;error&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;: &lt;span class="kt"&gt;any&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;archive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;output&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The traversal uses &lt;code&gt;lstat&lt;/code&gt; and rejects symbolic links. Archive entry names come from &lt;code&gt;path.relative(approvedRoot, filePath)&lt;/code&gt;, so they do not expose absolute host paths. The synchronous directory calls keep this local example compact; a service should use asynchronous traversal with bounded concurrency.&lt;/p&gt;
&lt;p&gt;Once all files have been processed, it finalizes the archive, effectively creating the zip file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;archive&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;finalize&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Also listen for errors from both the archive and output streams in production, create the destination atomically, and apply file-count and size limits before accepting untrusted workloads.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;Node.js can automate archive creation, but filesystem input needs an explicit trust boundary. Before using this pattern outside a controlled local script, validate roots and entry names, handle stream failures, and test extraction with representative nested paths.&lt;/p&gt;
&lt;p&gt;Remember to handle file and directory paths carefully and ensure that your script has appropriate permissions to read and write where necessary.&lt;/p&gt;</description></item><item><title>Query S3 Buckets Safely with the AWS CLI and JMESPath</title><link>https://www.yopa.page/blog/2023-06-26-aws-s3api-list-buckets-with-jmespath-queries.html</link><pubDate>Mon, 26 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-06-26-aws-s3api-list-buckets-with-jmespath-queries.html</guid><description>
&lt;p&gt;&lt;code&gt;aws s3api list-buckets&lt;/code&gt; lists general-purpose buckets owned by the authenticated account. AWS CLI&amp;rsquo;s &lt;code&gt;--query&lt;/code&gt; option uses JMESPath and can both project fields and filter by values. An older version of this article incorrectly said value filtering required &lt;code&gt;jq&lt;/code&gt; or &lt;code&gt;grep&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;First verify the identity, especially when several profiles are configured:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws sts get-caller-identity --profile engineering-dev
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;List bucket names:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws s3api list-buckets &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --profile engineering-dev &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --query &lt;span class="s1"&gt;&amp;#39;Buckets[].Name&amp;#39;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --output text
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Filter for an exact name in JMESPath:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws s3api list-buckets &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --profile engineering-dev &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --query &lt;span class="s2"&gt;&amp;#34;Buckets[?Name==&amp;#39;example-bucket&amp;#39;].Name&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --output text
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;For automation, an empty text result is ambiguous unless the script checks it explicitly. Avoid piping to unanchored &lt;code&gt;grep&lt;/code&gt;, where &lt;code&gt;logs&lt;/code&gt; could accidentally match &lt;code&gt;logs-archive&lt;/code&gt; and output formatting can change.&lt;/p&gt;
&lt;h2 id="listing-is-not-always-the-right-check"&gt;
&lt;a href="#listing-is-not-always-the-right-check" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Listing is not always the right check
&lt;/h2&gt;
&lt;p&gt;If the application already knows the bucket name and needs to determine whether its current identity can address it, use &lt;code&gt;head-bucket&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; aws s3api head-bucket &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --bucket example-bucket &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --profile engineering-dev 2&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;bucket is reachable&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;bucket is missing or access is denied&amp;#34;&lt;/span&gt; &amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;exit&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;For security reasons, error details may not always distinguish a nonexistent bucket from one the caller cannot access. Do not turn that ambiguity into a false “does not exist” claim. Creating a bucket based only on a failed head request can race or target the wrong account.&lt;/p&gt;
&lt;h2 id="pagination-and-scale"&gt;
&lt;a href="#pagination-and-scale" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pagination and scale
&lt;/h2&gt;
&lt;p&gt;Current &lt;code&gt;ListBuckets&lt;/code&gt; supports pagination and optional prefix/Region parameters. AWS strongly recommends paginated requests; unpaginated requests are rejected for accounts with an approved general-purpose bucket quota above 10,000. AWS CLI paginates by default, but scripts that set &lt;code&gt;--no-paginate&lt;/code&gt; or call the API directly must handle this deliberately. Directory buckets are not returned by this operation.&lt;/p&gt;
&lt;p&gt;Examples:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws s3api list-buckets --prefix team-a- --max-items &lt;span class="m"&gt;100&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws s3api list-buckets --bucket-region us-east-1 --max-items &lt;span class="m"&gt;100&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The exact CLI JSON skeleton is not a stable programmatic contract. For application logic, use an AWS SDK paginator and typed response rather than parsing human-oriented text output.&lt;/p&gt;
&lt;h2 id="verification-checklist"&gt;
&lt;a href="#verification-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Verification checklist
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Confirm account and role with STS.&lt;/li&gt;
&lt;li&gt;Test exact match, no match, and names with similar prefixes.&lt;/li&gt;
&lt;li&gt;Test access denied separately from an absent name.&lt;/li&gt;
&lt;li&gt;Preserve pagination in large-account scripts.&lt;/li&gt;
&lt;li&gt;Grant only &lt;code&gt;s3:ListAllMyBuckets&lt;/code&gt; or specific bucket actions actually required.&lt;/li&gt;
&lt;li&gt;Do not print sensitive account inventories into shared CI logs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Official documentation reviewed on &lt;strong&gt;2026-08-01&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/reference/s3api/list-buckets.html"&gt;&lt;code&gt;list-buckets&lt;/code&gt; AWS CLI reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/reference/s3api/head-bucket.html"&gt;&lt;code&gt;head-bucket&lt;/code&gt; AWS CLI reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-usage-filter.html"&gt;AWS CLI filtering&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Fish Shell for Interactive Development</title><link>https://www.yopa.page/blog/2023-06-25-embracing-fish-shell.html</link><pubDate>Sun, 25 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-06-25-embracing-fish-shell.html</guid><description>
&lt;p&gt;The command-line interface (CLI) remains an integral tool in every developer&amp;rsquo;s toolkit. It allows developers to interact with the operating system and perform tasks such as file manipulation, process control, and even software development. Bash has long been the default shell for most Linux distributions and macOS. However, there is an alternative gaining popularity - the Friendly Interactive Shell, also known as Fish.&lt;/p&gt;
&lt;p&gt;Fish is a modern and user-friendly command-line shell designed to provide a rich scripting environment, out-of-the-box intelligent features, and a visually appealing interface. Here are some reasons why developers are embracing Fish shell as their new command-line partner.&lt;/p&gt;
&lt;h2 id="1-intelligent-autocompletion"&gt;
&lt;a href="#1-intelligent-autocompletion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;1. Intelligent Autocompletion&lt;/strong&gt;
&lt;/h2&gt;
&lt;p&gt;One of Fish&amp;rsquo;s most praised features is its intelligent and robust autocomplete functionality. Unlike Bash, Fish can suggest commands based on the user&amp;rsquo;s history and often-used commands. Fish even completes commands based on man page entries, making it easier to use and learn new commands.&lt;/p&gt;
&lt;h2 id="2-syntax-highlighting"&gt;
&lt;a href="#2-syntax-highlighting" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;2. Syntax Highlighting&lt;/strong&gt;
&lt;/h2&gt;
&lt;p&gt;Fish shell provides syntax highlighting right on the command line, an uncommon feature for shells. Incorrect commands are highlighted in red, making it easier to spot mistakes. Valid file paths, on the other hand, are highlighted in a different color, providing quick visual feedback.&lt;/p&gt;
&lt;h2 id="3-web-based-configuration"&gt;
&lt;a href="#3-web-based-configuration" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;3. Web-Based Configuration&lt;/strong&gt;
&lt;/h2&gt;
&lt;p&gt;Fish includes a web-based configuration system, known as Fish Config. This allows for a graphical user interface to change shell appearance, variables, and functions without the need for text-based configuration files.&lt;/p&gt;
&lt;h2 id="4-improved-scripting"&gt;
&lt;a href="#4-improved-scripting" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;4. Improved Scripting&lt;/strong&gt;
&lt;/h2&gt;
&lt;p&gt;Fish offers a more straightforward scripting syntax than other shells like Bash. Its syntax is designed to be simpler and more consistent, reducing the complexity involved in writing shell scripts.&lt;/p&gt;
&lt;h2 id="5-excellent-documentation"&gt;
&lt;a href="#5-excellent-documentation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;5. Excellent Documentation&lt;/strong&gt;
&lt;/h2&gt;
&lt;p&gt;Fish boasts an extensive and well-structured documentation, accessible directly through the command line with the &lt;code&gt;help&lt;/code&gt; command. This makes learning Fish and troubleshooting issues a breeze.&lt;/p&gt;
&lt;h2 id="6-event-handling"&gt;
&lt;a href="#6-event-handling" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;6. Event Handling&lt;/strong&gt;
&lt;/h2&gt;
&lt;p&gt;Fish supports event handling mechanism that triggers functions on shell events, such as variable changes or process exits. This allows developers to automate certain tasks in response to specific events.&lt;/p&gt;
&lt;h2 id="7-extensive-customizability"&gt;
&lt;a href="#7-extensive-customizability" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;7. Extensive Customizability&lt;/strong&gt;
&lt;/h2&gt;
&lt;p&gt;Fish allows developers to customize the prompt, color scheme, and even the shell&amp;rsquo;s behavior, thanks to its powerful and straightforward scripting capabilities.&lt;/p&gt;
&lt;p&gt;In conclusion, Fish shell offers a modern, intelligent, and user-friendly alternative to traditional shells. Its rich features, coupled with a focus on usability and consistency, make it a game-changing tool for developers. Whether you&amp;rsquo;re a seasoned developer looking for a more productive shell or a novice seeking a friendly introduction to the command line, Fish shell is worth considering.&lt;/p&gt;
&lt;p&gt;Give it a try, and you might find your command-line experience transformed.&lt;/p&gt;</description></item><item><title>Understanding Origin and Branches in GitHub</title><link>https://www.yopa.page/blog/2023-06-24-understanding-origin-and-branches-in-github.html</link><pubDate>Sat, 24 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-06-24-understanding-origin-and-branches-in-github.html</guid><description>
&lt;h2 id="introduction"&gt;
&lt;a href="#introduction" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Introduction
&lt;/h2&gt;
&lt;p&gt;When first delving into the world of version control systems, specifically Git and GitHub, two terms you are likely to encounter often are &amp;ldquo;origin&amp;rdquo; and &amp;ldquo;branch&amp;rdquo;. However, their usage and what they represent can often lead to confusion. In this article, we will clear up these ambiguities and give you a clear understanding of what &amp;ldquo;origin&amp;rdquo; and &amp;ldquo;branch&amp;rdquo; are in GitHub.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="origin-the-remote-repository"&gt;
&lt;a href="#origin-the-remote-repository" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Origin: The Remote Repository
&lt;/h2&gt;
&lt;p&gt;In Git and by extension, GitHub, &amp;ldquo;origin&amp;rdquo; is not a branch as some might mistakenly think. Instead, it is the conventional name given to the default remote repository. Here&amp;rsquo;s a little context for clarity.&lt;/p&gt;
&lt;p&gt;Git is a distributed version control system, which means you work with a local copy of the entire project on your computer. &amp;ldquo;Origin&amp;rdquo; is a reference to the remote repository from which the project was originally cloned or copied.&lt;/p&gt;
&lt;p&gt;When you clone a repository from GitHub, Git automatically names this remote repository &amp;ldquo;origin&amp;rdquo;. Hence, when you run commands like &lt;code&gt;git push origin master&lt;/code&gt;, you are instructing Git to push your changes to the &amp;ldquo;master&amp;rdquo; branch on the &amp;ldquo;origin&amp;rdquo; remote repository.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="branch-your-development-line"&gt;
&lt;a href="#branch-your-development-line" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Branch: Your Development Line
&lt;/h2&gt;
&lt;p&gt;While &amp;ldquo;origin&amp;rdquo; refers to the remote repository, a &amp;ldquo;branch&amp;rdquo; is an entirely different concept. A branch represents a separate line of development within your project.&lt;/p&gt;
&lt;p&gt;The &amp;ldquo;master&amp;rdquo; branch (or &amp;ldquo;main&amp;rdquo; branch as many repositories are now naming it) is the default branch. It typically represents the stable, production-ready state of your code. Other branches are used for the development of new features, fixing bugs, or experimenting without affecting the stable code.&lt;/p&gt;
&lt;p&gt;Once the work on these branches is completed and tested, they can be merged back into the master (or main) branch.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="key-differences"&gt;
&lt;a href="#key-differences" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Key Differences
&lt;/h2&gt;
&lt;p&gt;To sum up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Origin&lt;/strong&gt;: The default remote repository where your code resides. It&amp;rsquo;s the original location from which your project was cloned.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Branch&lt;/strong&gt;: An independent line of development within your project. The master/main branch being the default and typically production-ready branch.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="wrapping-up"&gt;
&lt;a href="#wrapping-up" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Wrapping up!
&lt;/h2&gt;
&lt;p&gt;Understanding the difference between &amp;ldquo;origin&amp;rdquo; and &amp;ldquo;branch&amp;rdquo; can go a long way in improving your efficiency with Git and GitHub. Remember that &amp;ldquo;origin&amp;rdquo; refers to your remote repository, while a &amp;ldquo;branch&amp;rdquo; is your line of development within the project.&lt;/p&gt;</description></item><item><title>Git Vs GitHub</title><link>https://www.yopa.page/blog/2023-06-23-git-vs-github.html</link><pubDate>Fri, 23 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-06-23-git-vs-github.html</guid><description>
&lt;h2 id="introduction"&gt;
&lt;a href="#introduction" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Introduction
&lt;/h2&gt;
&lt;p&gt;The terms &amp;ldquo;Git&amp;rdquo; and &amp;ldquo;GitHub&amp;rdquo; are often mentioned together, and for those new to programming or version control systems, this can sometimes lead to confusion. In essence, Git is a version control system, and GitHub is an online service that hosts Git repositories and enhances collaborative work. Let&amp;rsquo;s delve deeper into their individual roles and the differences between them.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="understanding-git"&gt;
&lt;a href="#understanding-git" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Understanding-Git
&lt;/h2&gt;
&lt;p&gt;Git is a distributed version control system (VCS) that was created by Linus Torvalds, who is also credited with creating Linux. The primary purpose of any VCS is to manage changes to computer programs, documents, large websites, or any other information collections. Git is designed to handle everything from small to extensive projects with speed and efficiency.&lt;/p&gt;
&lt;p&gt;Multiple developers can work on the same codebase using Git, without causing conflicts. This is made possible by Git&amp;rsquo;s ability to create separate environments, known as &amp;ldquo;branches&amp;rdquo;. These branches allow developers to work on features or bug fixes independently, and then merge their changes back into the main codebase when they&amp;rsquo;re ready.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="what-is-github"&gt;
&lt;a href="#what-is-github" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What-is-GitHub?
&lt;/h2&gt;
&lt;p&gt;GitHub, in contrast, is a web-based service that hosts Git repositories. Along with the version control functionality provided by Git, GitHub adds extra features like bug tracking, task management, and project wikis. Essentially, GitHub facilitates team collaboration on projects.&lt;/p&gt;
&lt;p&gt;Multiple people can work on code simultaneously, propose changes, review code, discuss issues, and contribute to a codebase in a structured, version-controlled environment on GitHub. GitHub also simplifies &amp;ldquo;forking&amp;rdquo; projects (creating a copy of someone else&amp;rsquo;s project to your account), making changes to that project, and then proposing your changes back to the original project via a &amp;ldquo;pull request&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Furthermore, GitHub has a social aspect to it, where users can follow each other, rate each other&amp;rsquo;s contributions, showcase their own projects, and more.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id="the-differences-between-git-and-github"&gt;
&lt;a href="#the-differences-between-git-and-github" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The-Differences-Between-Git-and-GitHub
&lt;/h2&gt;
&lt;p&gt;To sum it all up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Git&lt;/strong&gt;: A version control tool that tracks changes and allows for seamless collaboration in code development.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub&lt;/strong&gt;: An online service that hosts Git-managed repositories, providing a platform for team collaboration. It also adds its own features for project management and community interaction.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;In essence, Git and GitHub complement each other: Git as a powerful tool for version control and GitHub as a platform to collaborate and manage Git repositories. While they are often used together, they perform different roles, each significant in its own right.&lt;/p&gt;</description></item><item><title>Install and Update Salesforce CLI Safely</title><link>https://www.yopa.page/blog/2026-08-01-install-and-update-salesforce-cli.html</link><pubDate>Thu, 22 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-install-and-update-salesforce-cli.html</guid><description>
&lt;p&gt;This guide was verified against Salesforce&amp;rsquo;s current CLI documentation on 2026-08-01.&lt;/p&gt;
&lt;h2 id="why-the-old-workflow-changed"&gt;
&lt;a href="#why-the-old-workflow-changed" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why the old workflow changed
&lt;/h2&gt;
&lt;p&gt;The standalone &lt;code&gt;sfdx&lt;/code&gt; CLI has been unsupported since April 2023. The supported executable is &lt;code&gt;sf&lt;/code&gt;, distributed through Salesforce installers and the &lt;code&gt;@salesforce/cli&lt;/code&gt; npm package. An old troubleshooting habit was to uninstall packages, delete every &lt;code&gt;.sfdx&lt;/code&gt; directory, and reinstall. That mixes three different concerns—program files, configuration, and authorization—and can destroy working credentials without fixing the binary selected by &lt;code&gt;PATH&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="start-with-the-installation-owner"&gt;
&lt;a href="#start-with-the-installation-owner" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Start with the installation owner
&lt;/h2&gt;
&lt;p&gt;Before changing anything, identify every executable and its version:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;type&lt;/span&gt; -a sf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;command&lt;/span&gt; -v sf
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf version --verbose
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm list --global --depth&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt; @salesforce/cli
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;On macOS, &lt;code&gt;type -a&lt;/code&gt; is especially useful because a native installer, Homebrew-managed path, and an npm global installed under a Node version manager can coexist. Update with the same channel that owns the active executable. A Salesforce installer build can self-update with &lt;code&gt;sf update&lt;/code&gt;; an npm installation is updated through npm:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm install --global @salesforce/cli@latest
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf version --verbose
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf doctor
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Do not run both approaches merely to be safe. Multiple owners make later debugging harder.&lt;/p&gt;
&lt;h2 id="preserve-configuration-and-authentication"&gt;
&lt;a href="#preserve-configuration-and-authentication" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Preserve configuration and authentication
&lt;/h2&gt;
&lt;p&gt;CLI state is not disposable program code. Before a repair, list the org aliases you rely on and confirm that your team has a documented way to reauthenticate. Never copy authorization files into a repository or ticket. Do not use &lt;code&gt;sudo rm -rf&lt;/code&gt; against CLI state directories. If a support procedure calls for clearing a specific cache, back it up and limit the change to that diagnosed cache.&lt;/p&gt;
&lt;p&gt;For CI, pin an intentional CLI version in the build image or lockfile and update it through a reviewed dependency change. Do not let every pipeline pull &lt;code&gt;latest&lt;/code&gt; without validation. Authentication material belongs in the CI secret store, and the build log must not print auth URLs or tokens.&lt;/p&gt;
&lt;h2 id="alternatives-and-tradeoffs"&gt;
&lt;a href="#alternatives-and-tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Alternatives and tradeoffs
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;The native installer is a simple default for a developer workstation and avoids coupling the CLI to an &lt;code&gt;nvm&lt;/code&gt; Node version.&lt;/li&gt;
&lt;li&gt;npm is useful when a team already controls Node and global-tool versions, but each Node installation can have a different global package directory.&lt;/li&gt;
&lt;li&gt;A pinned container gives CI reproducibility, at the cost of maintaining and scanning the image.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Record &lt;code&gt;type -a sf&lt;/code&gt;, &lt;code&gt;sf version --verbose&lt;/code&gt;, and the owning package manager.&lt;/li&gt;
&lt;li&gt;Remove legacy &lt;code&gt;sfdx-cli&lt;/code&gt; only through the package manager that installed it.&lt;/li&gt;
&lt;li&gt;Install or update one supported &lt;code&gt;sf&lt;/code&gt; distribution.&lt;/li&gt;
&lt;li&gt;Open a fresh shell and confirm that the intended binary appears first.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;sf doctor&lt;/code&gt;, then &lt;code&gt;sf org list auth&lt;/code&gt; without sharing its sensitive output.&lt;/li&gt;
&lt;li&gt;Execute one read-only command against a nonproduction org.&lt;/li&gt;
&lt;li&gt;Update scripts from &lt;code&gt;sfdx force:*&lt;/code&gt; to their current &lt;code&gt;sf&lt;/code&gt; equivalents separately; a binary update does not make old documentation correct.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="official-sources"&gt;
&lt;a href="#official-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Official sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-guide/guide/install-sfdx-cli.html"&gt;Install Salesforce CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-plugin/guide/migrate-sfdx-sf.html"&gt;Migrate from sfdx to sf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-reference/guide/cli_reference.html"&gt;Salesforce CLI command reference&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Choose the Right Salesforce Configuration Tool</title><link>https://www.yopa.page/blog/2026-08-01-choose-salesforce-configuration-tools.html</link><pubDate>Wed, 21 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-choose-salesforce-configuration-tools.html</guid><description>
&lt;p&gt;Verified on 2026-08-01. “Record-based configuration” is not one Salesforce feature, and Workflow Rules are a legacy automation path.&lt;/p&gt;
&lt;p&gt;Choose by responsibility:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Record types select business processes, picklist values, and experiences for materially different processes.&lt;/li&gt;
&lt;li&gt;Page layouts and Dynamic Forms control presentation; they are not authorization.&lt;/li&gt;
&lt;li&gt;Custom metadata stores deployable application configuration.&lt;/li&gt;
&lt;li&gt;Flow is the strategic declarative automation tool; Apex remains appropriate for logic that needs code-level control, testing, or scale.&lt;/li&gt;
&lt;li&gt;Permission sets control access.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Avoid a record type for every minor variation. Start with the business process and security boundary, then select the smallest mechanism. The tradeoff is governance: declarative tools are approachable but still need source control, tests, owners, and limits.&lt;/p&gt;
&lt;p&gt;Migration: inventory Workflow Rules and overloaded record types, add regression tests, migrate automation through Salesforce&amp;rsquo;s supported Flow tooling, activate in a controlled release, and monitor failed Flow interviews.&lt;/p&gt;
&lt;p&gt;References: &lt;a href="https://help.salesforce.com/s/articleView?id=sf.flow_migrate_to_flow.htm&amp;amp;type=5"&gt;Migrate to Flow&lt;/a&gt;, &lt;a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_custommetadatatypes.htm"&gt;Custom Metadata Types&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="start-with-the-decision-not-the-feature"&gt;
&lt;a href="#start-with-the-decision-not-the-feature" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Start with the decision, not the feature
&lt;/h2&gt;
&lt;p&gt;Write down what varies, who may change it, whether it moves between orgs, and whether it affects UI, access, or automation. A record type is appropriate only when one object supports materially different processes. It should not become a generic flag for every conditional.&lt;/p&gt;
&lt;h2 id="step-by-step-example"&gt;
&lt;a href="#step-by-step-example" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step-by-step example
&lt;/h2&gt;
&lt;p&gt;Suppose Projects have Internal and Client delivery processes. Use record types only if picklist values or process stages truly differ. Use Dynamic Forms for conditional field visibility, permission sets for access, custom metadata for deployable thresholds, and a record-triggered Flow for automation. Then:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Define developer names and ownership in source control.&lt;/li&gt;
&lt;li&gt;Build permission sets before page visibility rules so hidden UI is never mistaken for security.&lt;/li&gt;
&lt;li&gt;Put environment-independent policy in custom metadata, not hard-coded record IDs.&lt;/li&gt;
&lt;li&gt;Bulk-test the Flow, including update recursion and failure paths.&lt;/li&gt;
&lt;li&gt;Deploy to a sandbox, assign to a pilot permission-set group, and inspect failed interviews.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="alternatives-and-tradeoffs"&gt;
&lt;a href="#alternatives-and-tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Alternatives and tradeoffs
&lt;/h2&gt;
&lt;p&gt;One record type plus Dynamic Forms reduces administrative branching but cannot represent different sales/support business processes by itself. Custom settings can hold runtime configuration, while custom metadata is generally better for deployable configuration. Apex provides transaction control and reusable abstractions but requires code ownership. Flow is accessible and observable, yet large monolithic flows become as difficult to maintain as monolithic code.&lt;/p&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;p&gt;Inventory record types, layouts, Workflow Rules, Process Builder processes, validation rules, permissions, and hard-coded IDs together. Map each behavior to an owner and test. Use the supported migration tooling as a starting point, not proof of equivalence. Disable old automation only after side-by-side tests show that ordering, recursion, scheduled paths, and error handling match. Keep rollback artifacts until production monitoring is clean.&lt;/p&gt;
&lt;h2 id="verification"&gt;
&lt;a href="#verification" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Verification
&lt;/h2&gt;
&lt;p&gt;Test each persona, record type, create/update/bulk transaction, and denied field. Confirm metadata deploys without org-specific IDs. Monitor Flow errors and business outcomes after release. The best configuration is the smallest set of mechanisms whose responsibilities a future maintainer can explain.&lt;/p&gt;</description></item><item><title>Retreive GitHub Pull Requests Code</title><link>https://www.yopa.page/blog/2023-06-20-retreive-github-pull-requests-code.html</link><pubDate>Tue, 20 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-06-20-retreive-github-pull-requests-code.html</guid><description>
&lt;p&gt;When collaborating on a shared repository, it&amp;rsquo;s typical to review pull requests (PRs) made by other contributors. While it&amp;rsquo;s possible to view these PRs through the GitHub web interface, there&amp;rsquo;s an often-overlooked alternative that provides a more thorough review process - reviewing them directly from your local machine via the command line.&lt;/p&gt;
&lt;h2 id="the-advantages-of-local-review"&gt;
&lt;a href="#the-advantages-of-local-review" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Advantages of Local Review
&lt;/h2&gt;
&lt;p&gt;By fetching and checking out pull requests locally, you&amp;rsquo;re not just reviewing the code - you&amp;rsquo;re running it on your machine. This ability to execute the code offers a more practical and precise code review. It allows you to explore how the changes interact with the existing codebase, test the code in various scenarios, and identify potential bugs or issues that may not be immediately apparent when reviewing the code on the web interface.&lt;/p&gt;
&lt;p&gt;Additionally, developers often find it more comfortable and efficient to read and understand code within their local development environment, as they can leverage familiar tools and configurations.&lt;/p&gt;
&lt;p&gt;Without further ado, let&amp;rsquo;s delve into the steps needed to review GitHub PRs locally.&lt;/p&gt;
&lt;h2 id="step-1-fetch-the-pr"&gt;
&lt;a href="#step-1-fetch-the-pr" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 1: Fetch the PR
&lt;/h2&gt;
&lt;p&gt;Firstly, you need to fetch the PR from the remote repository using this git command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git fetch origin pull/PR_NUMBER/head:pr-PR_NUMBER
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Remember to replace &lt;code&gt;PR_NUMBER&lt;/code&gt; with the actual number of the PR. For example, if you&amp;rsquo;re fetching pull request #123, the command would be:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git fetch origin pull/123/head:pr-123
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="step-2-checkout-the-pr"&gt;
&lt;a href="#step-2-checkout-the-pr" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 2: Checkout the PR
&lt;/h2&gt;
&lt;p&gt;Having fetched the PR, it&amp;rsquo;s now time to check it out into a new branch with the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git checkout pr-PR_NUMBER
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Again, replace &lt;code&gt;PR_NUMBER&lt;/code&gt; with the actual number of the PR. So, if the PR number was 123, you&amp;rsquo;d use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git checkout pr-123
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This command switches your working directory to the new branch (&lt;code&gt;pr-123&lt;/code&gt; in our example), giving you access to the proposed changes.&lt;/p&gt;
&lt;h2 id="step-3-review-and-run-the-pr"&gt;
&lt;a href="#step-3-review-and-run-the-pr" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 3: Review and Run the PR
&lt;/h2&gt;
&lt;p&gt;With the PR branch checked out, you can now proceed with the code review. But now, you have the advantage of being able to actually run the code. This allows you to identify potential runtime errors, understand how the new changes behave under different scenarios, and check the compatibility with your existing codebase.&lt;/p&gt;
&lt;p&gt;After a thorough assessment, you&amp;rsquo;re now ready to provide constructive feedback, propose changes, or approve the pull request.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;Although the GitHub web interface is a convenient tool for reviewing PRs, checking them out locally allows for a more comprehensive review process. By fetching and running the changes proposed in a pull request on your machine, you can deliver higher quality feedback and help ensure the stability and efficiency of your shared codebase. After your review, remember to switch back to your original branch and clean up the PR branches to keep your local repo organized.&lt;/p&gt;</description></item><item><title>Safely Remove Unwanted Files from a Pull Request</title><link>https://www.yopa.page/blog/2026-08-01-remove-unwanted-files-from-a-pull-request.html</link><pubDate>Mon, 19 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-remove-unwanted-files-from-a-pull-request.html</guid><description>
&lt;p&gt;Removing a file from a pull request is not one Git operation. The safe command depends on whether the file is untracked, staged, committed, pushed, or intentionally deleted. Treating these states as interchangeable is how unrelated work gets lost.&lt;/p&gt;
&lt;p&gt;This guide was verified on 2026-08-01 against the official documentation for &lt;a href="https://git-scm.com/docs/git-status"&gt;&lt;code&gt;git status&lt;/code&gt;&lt;/a&gt;, &lt;a href="https://git-scm.com/docs/git-restore"&gt;&lt;code&gt;git restore&lt;/code&gt;&lt;/a&gt;, and &lt;a href="https://docs.github.com/en/pull-requests"&gt;GitHub pull requests&lt;/a&gt;. Run &lt;code&gt;git status --short&lt;/code&gt; before and after every correction.&lt;/p&gt;
&lt;h2 id="start-with-the-pull-requests-actual-difference"&gt;
&lt;a href="#start-with-the-pull-requests-actual-difference" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Start with the pull request&amp;rsquo;s actual difference
&lt;/h2&gt;
&lt;p&gt;Fetch the target branch and inspect the merge-base comparison used by a pull request:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git fetch origin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git status --short
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git diff --name-status origin/main...HEAD
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git diff origin/main...HEAD -- path/to/file
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Replace &lt;code&gt;origin/main&lt;/code&gt; with the PR&amp;rsquo;s real base branch. Three dots compare the merge base with the proposed branch tip; two dots compare two endpoint trees and can include unrelated base-branch movement.&lt;/p&gt;
&lt;p&gt;If GitHub CLI is available, &lt;code&gt;gh pr diff --name-only&lt;/code&gt; is another useful view. Neither command changes files.&lt;/p&gt;
&lt;h2 id="case-1-the-file-is-only-staged"&gt;
&lt;a href="#case-1-the-file-is-only-staged" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Case 1: the file is only staged
&lt;/h2&gt;
&lt;p&gt;Keep the working copy but remove it from the next commit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git restore --staged -- path/to/file
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git status --short
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;If the file should never be committed, add an appropriate shared pattern to &lt;code&gt;.gitignore&lt;/code&gt;, or use &lt;code&gt;.git/info/exclude&lt;/code&gt; for a personal local-only pattern. Ignore rules do not stop tracking a file that is already committed.&lt;/p&gt;
&lt;h2 id="case-2-discard-an-uncommitted-edit-to-a-tracked-file"&gt;
&lt;a href="#case-2-discard-an-uncommitted-edit-to-a-tracked-file" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Case 2: discard an uncommitted edit to a tracked file
&lt;/h2&gt;
&lt;p&gt;Preview it, then restore only that path:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git diff -- path/to/file
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git restore --source&lt;span class="o"&gt;=&lt;/span&gt;HEAD --worktree -- path/to/file
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This intentionally discards the path&amp;rsquo;s unstaged change. Copy or stash the file first if any part is worth keeping. Avoid repository-wide &lt;code&gt;git restore .&lt;/code&gt;, &lt;code&gt;git checkout -- .&lt;/code&gt;, or &lt;code&gt;git reset --hard&lt;/code&gt;: their scope is much larger than the stated task.&lt;/p&gt;
&lt;h2 id="case-3-the-unwanted-change-is-already-committed"&gt;
&lt;a href="#case-3-the-unwanted-change-is-already-committed" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Case 3: the unwanted change is already committed
&lt;/h2&gt;
&lt;p&gt;Restore that file from the PR base, then commit the correction:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git fetch origin
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git restore --source&lt;span class="o"&gt;=&lt;/span&gt;origin/main --staged --worktree -- path/to/file
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git diff --cached -- path/to/file
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;Restore path excluded from this pull request&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This preserves published history and makes the correction reviewable. It works whether the PR accidentally modified or deleted the tracked file. If the branch deliberately needs a clean, rebased history, interactive rebase is an alternative, but rewriting pushed commits requires coordination and normally a guarded &lt;code&gt;--force-with-lease&lt;/code&gt;. It is not the default cleanup method.&lt;/p&gt;
&lt;h2 id="case-4-remove-a-file-from-the-project"&gt;
&lt;a href="#case-4-remove-a-file-from-the-project" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Case 4: remove a file from the project
&lt;/h2&gt;
&lt;p&gt;If the PR is supposed to delete the file, use &lt;code&gt;git rm&lt;/code&gt; and review the staged deletion:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git rm -- path/to/file
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git diff --cached -- path/to/file
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit -m &lt;span class="s2"&gt;&amp;#34;Remove obsolete file&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;To stop tracking a generated or local file while keeping the working copy, first add the correct ignore rule and then use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git rm --cached -- path/to/file
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git diff --cached -- path/to/file
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This is a repository decision because collaborators will also stop receiving that path.&lt;/p&gt;
&lt;h2 id="forks-generated-files-and-secrets"&gt;
&lt;a href="#forks-generated-files-and-secrets" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Forks, generated files, and secrets
&lt;/h2&gt;
&lt;p&gt;For a PR from a fork, push the correction to the same head branch. Creating another branch does not update the existing PR automatically.&lt;/p&gt;
&lt;p&gt;Generated lockfiles, snapshots, and migrations should not be removed merely because they are large. Confirm the repository policy and reproduce the generator instead of hand-editing output.&lt;/p&gt;
&lt;p&gt;If the file contains a credential, removing it in a later commit does not remove it from Git history. Revoke or rotate the secret immediately, notify the repository owner, and follow GitHub&amp;rsquo;s &lt;a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/removing-sensitive-data-from-a-repository"&gt;sensitive-data removal guidance&lt;/a&gt;. History rewriting is a coordinated incident-response action.&lt;/p&gt;
&lt;h2 id="verification-checklist"&gt;
&lt;a href="#verification-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Verification checklist
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git status --short
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git diff --name-status origin/main...HEAD
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git diff origin/main...HEAD -- path/to/file
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The final PR diff should contain only intended changes. Run the project&amp;rsquo;s tests, check generated artifacts according to repository policy, and read the web PR once more before requesting review.&lt;/p&gt;
&lt;p&gt;When several files are involved, repeat the path-scoped procedure instead of widening the command to the repository root. A sequence of small, named correction commits is easier to review and can be squashed later if the repository prefers a compact history. The important property is observability: before each write, know which Git state will change; after each write, confirm it with &lt;code&gt;status&lt;/code&gt; and the PR diff. That discipline is more reliable than memorizing one cleanup command.&lt;/p&gt;</description></item><item><title>How to Add Tags in GitHub</title><link>https://www.yopa.page/blog/2023-06-18-how-to-add-tags-in-github.html</link><pubDate>Sun, 18 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-06-18-how-to-add-tags-in-github.html</guid><description>
&lt;p&gt;Tags in GitHub are a great way to mark specific points in your project&amp;rsquo;s history, such as significant releases or milestones. They provide a reference point for easier navigation and can be useful for collaboration and version control. In this article, we&amp;rsquo;ll explore two methods of adding tags in GitHub: using Git commands and using the web user interface (UI).&lt;/p&gt;
&lt;h2 id="adding-tags-using-git-command"&gt;
&lt;a href="#adding-tags-using-git-command" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Adding Tags Using Git Command
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Step 1: Clone the Repository&lt;/strong&gt;: Begin by cloning the repository to your local machine using the following command:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;git clone &amp;lt;repository-url&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;ol start="2"&gt;
&lt;li&gt;&lt;strong&gt;Step 2: Navigate to the Repository&lt;/strong&gt;: Move into the repository&amp;rsquo;s directory using the &lt;code&gt;cd&lt;/code&gt; command:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;cd &amp;lt;repository-directory&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;ol start="3"&gt;
&lt;li&gt;&lt;strong&gt;Step 3: Check Out the Desired Commit&lt;/strong&gt;: Use the &lt;code&gt;git log&lt;/code&gt; command to view the commit history. Find the commit you want to tag and note its commit hash. Then, check out the commit using the following command:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;git checkout &amp;lt;commit-hash&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;ol start="4"&gt;
&lt;li&gt;&lt;strong&gt;Step 4: Create the Tag&lt;/strong&gt;: Now, create the tag using the &lt;code&gt;git tag&lt;/code&gt; command. You can choose either an annotated or lightweight tag. For an annotated tag, use the following command:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;git tag -a &amp;lt;tag-name&amp;gt; -m &amp;#34;&amp;lt;tag-message&amp;gt;&amp;#34;
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;For a lightweight tag, use:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;git tag &amp;lt;tag-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;ol start="5"&gt;
&lt;li&gt;&lt;strong&gt;Step 5: Push the Tag to GitHub&lt;/strong&gt;: Finally, push the tag to the remote repository on GitHub with the &lt;code&gt;git push&lt;/code&gt; command:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;git push origin &amp;lt;tag-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="adding-tags-using-web-ui"&gt;
&lt;a href="#adding-tags-using-web-ui" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Adding Tags Using Web UI
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 1: Navigate to the Repository&lt;/strong&gt;: Open your web browser and go to GitHub. Navigate to the repository where you want to add the tag.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 2: Go to the Releases Section&lt;/strong&gt;: Once you&amp;rsquo;re in the repository, click on the &amp;ldquo;Releases&amp;rdquo; tab located near the top navigation bar.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 3: Create a New Release&lt;/strong&gt;: On the releases page, click on the &amp;ldquo;Create a new release&amp;rdquo; button.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 4: Fill in Release Details&lt;/strong&gt;: In the release creation form, enter the tag version, release title, and any other relevant information.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Step 5: Publish the Release&lt;/strong&gt;: After filling in the necessary details, click on the &amp;ldquo;Publish release&amp;rdquo; button to create the tag and publish the release.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;NOICE! You have successfully added a tag to your GitHub repository. Whether you prefer using Git commands or the web UI, both methods provide an efficient way to mark important points in your project&amp;rsquo;s history.&lt;/p&gt;
&lt;p&gt;Remember, tags are useful for organizing and tracking different versions of your project. They can also be used to trigger specific actions, such as deployment or CI/CD pipelines. Utilize tags wisely to enhance collaboration and streamline your development workflow.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: If you&amp;rsquo;re using a different Git platform or hosting service, the steps may vary slightly. However, the general concept of adding tags remains the same.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;My first article from my first house! ;)&lt;/p&gt;</description></item><item><title>Best Practices for Defining Paths for RESTful APIs</title><link>https://www.yopa.page/blog/2023-06-17-best-practices-for-defining-paths-for-restful-apis.html</link><pubDate>Sat, 17 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-06-17-best-practices-for-defining-paths-for-restful-apis.html</guid><description>
&lt;h2 id="best-practices-for-defining-paths-for-restful-apis"&gt;
&lt;a href="#best-practices-for-defining-paths-for-restful-apis" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Best Practices for Defining Paths for RESTful APIs
&lt;/h2&gt;
&lt;p&gt;When creating RESTful APIs, it&amp;rsquo;s important to adhere to the best practices for defining paths. Below are a few guidelines:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use nouns, not verbs:&lt;/strong&gt; RESTful APIs should use nouns (not verbs) to indicate the resources being manipulated. The HTTP methods (&lt;code&gt;GET&lt;/code&gt;, &lt;code&gt;POST&lt;/code&gt;, &lt;code&gt;PUT&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, etc.) should define the operation. For example, use &lt;code&gt;/users/{userId}&lt;/code&gt; instead of &lt;code&gt;/getUser/{userId}&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Pluralize resources:&lt;/strong&gt; It&amp;rsquo;s generally recommended to use plural forms of resources to indicate a collection. For instance, &lt;code&gt;/users&lt;/code&gt; represents all users, while &lt;code&gt;/users/{userId}&lt;/code&gt; represents a specific user.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Avoid deep nesting:&lt;/strong&gt; While it may be tempting to deeply nest resources, this can lead to complex and less manageable APIs. Instead, limit your API paths to two levels if possible. For example, use &lt;code&gt;/users/{userId}/posts&lt;/code&gt; rather than &lt;code&gt;/users/{userId}/posts/{postId}/comments&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Lowercase letters and hyphens:&lt;/strong&gt; Use lowercase letters in the path. If you need to separate two words, use hyphens (&lt;code&gt;-&lt;/code&gt;) instead of underscores (&lt;code&gt;_&lt;/code&gt;) or camel case.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Consistency:&lt;/strong&gt; Above all, be consistent in your naming and structuring conventions. Consistency helps to maintain clarity for all developers who are using your API.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Use Meaningful Resource Names&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Ensure that resource names are intuitive and meaningful. They should clearly indicate what kind of data is being represented by the API. For example, &lt;code&gt;/employees&lt;/code&gt; is a more intuitive resource name than &lt;code&gt;/people&lt;/code&gt; when you&amp;rsquo;re dealing with an API for a human resources application.&lt;/p&gt;
&lt;ol start="8"&gt;
&lt;li&gt;&lt;strong&gt;Avoid Query Parameters for Mandatory Data&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;While query parameters can provide flexibility, they should not be used for mandatory data. Mandatory data should be provided as path parameters instead. For instance, use &lt;code&gt;/users/{userId}&lt;/code&gt; instead of &lt;code&gt;/users?userId={userId}&lt;/code&gt;.&lt;/p&gt;
&lt;ol start="9"&gt;
&lt;li&gt;&lt;strong&gt;Versioning Your APIs&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;It&amp;rsquo;s recommended to include a version in your API path to prevent breaking changes for your API consumers. For instance, &lt;code&gt;/v1/users/{userId}&lt;/code&gt;. This allows you to evolve your API without interrupting service for existing clients.&lt;/p&gt;
&lt;h2 id="real-life-examples"&gt;
&lt;a href="#real-life-examples" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Real-Life Examples
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s look at a real-life example - an API for a blog application. Here are some endpoints that follow the best practices:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;GET /v1/posts&lt;/code&gt; - Retrieves all blog posts.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /v1/posts/{postId}&lt;/code&gt; - Retrieves a specific blog post.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;POST /v1/posts&lt;/code&gt; - Creates a new blog post.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PUT /v1/posts/{postId}&lt;/code&gt; - Updates a specific blog post.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DELETE /v1/posts/{postId}&lt;/code&gt; - Deletes a specific blog post.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;GET /v1/users/{userId}/posts&lt;/code&gt; - Retrieves all posts from a specific user.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Note that we have a version number in the paths, we&amp;rsquo;re using plural and meaningful resource names (&lt;code&gt;posts&lt;/code&gt;), and we&amp;rsquo;re not using query parameters for mandatory data (&lt;code&gt;postId&lt;/code&gt; and &lt;code&gt;userId&lt;/code&gt; are path parameters).&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Adhering to these best practices when defining paths will help to create APIs that are not only powerful and scalable, but also user-friendly and easy to maintain. Consistency, intuitive resource names, correct use of path and query parameters, and proper versioning are all essential for a well-structured API.&lt;/p&gt;</description></item><item><title>Path Parameters in Amazon API Gateway</title><link>https://www.yopa.page/blog/2023-06-16-aws-api-gateway-path-parameters.html</link><pubDate>Fri, 16 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-06-16-aws-api-gateway-path-parameters.html</guid><description>
&lt;p&gt;Path parameters identify a resource inside a route: &lt;code&gt;GET /users/{userId}&lt;/code&gt;. API Gateway matches the route and passes the captured value to the integration. The braces belong to the API Gateway route definition; a backend framework may use different syntax, such as Express &lt;code&gt;/users/:userId&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="http-api-and-rest-api-events"&gt;
&lt;a href="#http-api-and-rest-api-events" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
HTTP API and REST API events
&lt;/h2&gt;
&lt;p&gt;For a Lambda proxy integration, read the decoded route values from &lt;code&gt;pathParameters&lt;/code&gt; rather than parsing the raw path yourself.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;APIGatewayProxyHandlerV2&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aws-lambda&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;: &lt;span class="kt"&gt;APIGatewayProxyHandlerV2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathParameters&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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;userId&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="sr"&gt;/^[a-zA-Z0-9_-]{1,64}$/&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;statusCode&lt;/span&gt;: &lt;span class="kt"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;: &lt;span class="kt"&gt;JSON.stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;invalid userId&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Authorization must confirm the caller may read this user.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;statusCode&lt;/span&gt;: &lt;span class="kt"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt;: &lt;span class="kt"&gt;JSON.stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;userId&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Payload shapes differ between HTTP API payload versions and REST API proxy events. Type and test against the integration actually deployed. A greedy route such as &lt;code&gt;/{proxy+}&lt;/code&gt; captures multiple path segments; use it when the backend intentionally owns routing, not as a shortcut that hides the API contract.&lt;/p&gt;
&lt;h2 id="resource-modeling"&gt;
&lt;a href="#resource-modeling" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Resource modeling
&lt;/h2&gt;
&lt;p&gt;Use a path value for the identity or hierarchy of the addressed resource, for example &lt;code&gt;/orders/{orderId}&lt;/code&gt;. Use query parameters for filtering, sorting, pagination, and optional views, for example &lt;code&gt;/orders?status=open&amp;amp;cursor=...&lt;/code&gt;. “Required” does not automatically mean “path”: a required search filter can still be a query parameter, while a resource identifier naturally belongs in the path.&lt;/p&gt;
&lt;p&gt;Nested paths such as &lt;code&gt;/users/{userId}/posts/{postId}&lt;/code&gt; are useful when the relationship is part of the authorization or identity. Avoid deep nesting when the child has a stable independent identifier. Consistency and a documented OpenAPI contract matter more than a universal pluralization or depth rule.&lt;/p&gt;
&lt;h2 id="validation-and-security"&gt;
&lt;a href="#validation-and-security" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Validation and security
&lt;/h2&gt;
&lt;p&gt;A syntactically valid path parameter is not authorization. Prevent insecure direct object references by checking the caller&amp;rsquo;s access to the resolved resource. Validate length and character set, handle percent-encoding consistently, and do not concatenate an unchecked value into SQL, filesystem paths, log formats, or downstream URLs.&lt;/p&gt;
&lt;p&gt;Return consistent error semantics: usually &lt;code&gt;400&lt;/code&gt; for malformed input, &lt;code&gt;401/403&lt;/code&gt; for authentication or authorization failure, and &lt;code&gt;404&lt;/code&gt; when the resource is absent according to the API&amp;rsquo;s disclosure policy. Configure route-level throttling and observe latency and errors by route without putting sensitive IDs into high-cardinality metric dimensions.&lt;/p&gt;
&lt;h2 id="verification-checklist"&gt;
&lt;a href="#verification-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Verification checklist
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Test missing, empty-equivalent, oversized, Unicode, encoded slash, and invalid-character values.&lt;/li&gt;
&lt;li&gt;Confirm HTTP API or REST API event shape and payload version.&lt;/li&gt;
&lt;li&gt;Test a valid ID owned by another user.&lt;/li&gt;
&lt;li&gt;Verify greedy-route precedence and the deployment stage/base path.&lt;/li&gt;
&lt;li&gt;Keep the OpenAPI route, infrastructure definition, and handler tests aligned.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Official documentation reviewed on &lt;strong&gt;2026-08-01&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-routes.html"&gt;HTTP API routes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop-integrations-lambda.html"&gt;Lambda proxy integrations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-method-settings-method-request.html"&gt;REST API request parameters&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Use AWS Temporary Credentials Without Reading Cache Files</title><link>https://www.yopa.page/blog/2026-08-01-use-aws-temporary-credentials-without-reading-cache-files.html</link><pubDate>Thu, 15 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-use-aws-temporary-credentials-without-reading-cache-files.html</guid><description>
&lt;p&gt;Temporary credentials contain an access key ID, secret access key, and usually a session token. Applications need them to sign AWS requests, but applications usually should not retrieve, store, or refresh those values directly. They should ask a &lt;strong&gt;credential provider&lt;/strong&gt; when the SDK needs to sign a request.&lt;/p&gt;
&lt;p&gt;This distinction matters. Reading the first file under &lt;code&gt;~/.aws/sso/cache&lt;/code&gt;, extracting its access token, and running &lt;code&gt;aws sso get-role-credentials&lt;/code&gt; assumes an internal cache layout, may select another login, and can expose a bearer token through shell arguments. It also turns application code into a partial credential manager.&lt;/p&gt;
&lt;h2 id="one-application-different-providers"&gt;
&lt;a href="#one-application-different-providers" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
One application, different providers
&lt;/h2&gt;
&lt;p&gt;Use the SDK for JavaScript v3 default provider chain:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;S3Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ListBucketsCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@aws-sdk/client-s3&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;S3Client&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;region&lt;/span&gt;: &lt;span class="kt"&gt;process.env.AWS_REGION&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;us-east-1&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;s3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ListBucketsCommand&lt;/span&gt;&lt;span class="p"&gt;({}));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Buckets&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;There is deliberately no credential object. In local Node.js development, the provider can resolve &lt;code&gt;AWS_PROFILE&lt;/code&gt; from shared config, including IAM Identity Center. In ECS or EC2 it can resolve the attached role. In EKS or external CI it can resolve web identity. This keeps environment-specific authentication outside business code and allows refresh.&lt;/p&gt;
&lt;p&gt;Local operator flow:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws configure sso --profile engineering-dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws sso login --profile engineering-dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;AWS_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;engineering-dev aws sts get-caller-identity
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;AWS_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;engineering-dev node dist/list-buckets.js
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Deployed workload flow: attach a least-privilege workload role and run the same program without &lt;code&gt;AWS_PROFILE&lt;/code&gt;. Never copy a local SSO cache into a container image.&lt;/p&gt;
&lt;h2 id="when-to-choose-an-explicit-provider"&gt;
&lt;a href="#when-to-choose-an-explicit-provider" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
When to choose an explicit provider
&lt;/h2&gt;
&lt;p&gt;Use &lt;code&gt;fromIni({ profile })&lt;/code&gt; when a local tool intentionally accepts a named profile or assumes a role from it. Use &lt;code&gt;fromTemporaryCredentials&lt;/code&gt; for an explicit role-assumption chain. Use &lt;code&gt;fromTokenFile&lt;/code&gt; for web identity environments. These providers return functions so the SDK can refresh values. Avoid resolving once and spreading the raw keys into many clients.&lt;/p&gt;
&lt;p&gt;Do not let an untrusted request choose an arbitrary local profile or role ARN. Treat identity selection as configuration with an allowlist. Verify account and ARN at startup for tools that can mutate infrastructure.&lt;/p&gt;
&lt;h2 id="failure-and-observability"&gt;
&lt;a href="#failure-and-observability" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Failure and observability
&lt;/h2&gt;
&lt;p&gt;Differentiate “no credential source”, “interactive session expired”, “role trust denied”, and “service action denied”. The remedy for an expired workforce session is an explicit login—not scanning other cache files. Record the selected profile name, Region, account, role ARN, request ID, and error code where appropriate; never record secrets or tokens.&lt;/p&gt;
&lt;h2 id="alternatives-and-migration"&gt;
&lt;a href="#alternatives-and-migration" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Alternatives and migration
&lt;/h2&gt;
&lt;p&gt;AWS CLI commands already use the shared provider behavior, so a shell-only workflow may not need SDK code. A legacy program that only accepts environment variables may use the CLI-supported &lt;code&gt;export-credentials&lt;/code&gt; command for a bounded child process. Static IAM access keys should remain a last resort for unsupported workloads.&lt;/p&gt;
&lt;p&gt;Migration checklist:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Remove filesystem reads of SSO cache and access-token parsing.&lt;/li&gt;
&lt;li&gt;Remove shell calls to &lt;code&gt;sso get-role-credentials&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Use modular SDK clients and the default provider, or one documented explicit provider.&lt;/li&gt;
&lt;li&gt;Separate workforce, CI, and deployed workload authentication.&lt;/li&gt;
&lt;li&gt;Verify the expected account and least-privilege action set.&lt;/li&gt;
&lt;li&gt;Test expiration and automatic refresh during a long-running process.&lt;/li&gt;
&lt;li&gt;Delete copied credential files and revoke any accidentally exposed session.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="make-the-provider-boundary-visible-in-design"&gt;
&lt;a href="#make-the-provider-boundary-visible-in-design" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Make the provider boundary visible in design
&lt;/h2&gt;
&lt;p&gt;A useful application interface accepts an SDK client or a provider function, not three credential strings. That keeps authentication in the composition root and lets tests inject a client with a mock transport. It also avoids a common refresh bug: one module obtains temporary credentials, serializes them into configuration, and other modules continue using them after expiry.&lt;/p&gt;
&lt;p&gt;For role assumption, set a meaningful role session name and use source identity or session tags only when the trust and authorization design needs them. Keep role chains short because each hop adds policy reasoning and session-duration constraints. External IDs are for confused-deputy protection in third-party cross-account access; they are not secrets and do not replace a narrow trust principal.&lt;/p&gt;
&lt;p&gt;Operationally, alert on access denied patterns and unexpected role assumption, not on the presence of an access key ID. CloudTrail records the assumed-role session and is the place to connect activity to the workload. Use IAM Access Analyzer to validate policies and reduce permissions after observing the actions required by the application.&lt;/p&gt;
&lt;p&gt;Before removing a legacy credential path, run a canary with the provider-based build, force a credential refresh, and verify representative calls. Disable an old IAM key before deletion and watch for failed dependencies during a bounded rollback window. This separates a recoverable migration from indefinite dual authentication.&lt;/p&gt;
&lt;p&gt;Verified on &lt;strong&gt;2026-08-01&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="official-sources"&gt;
&lt;a href="#official-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Official sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/sdkref/latest/guide/standardized-credentials.html"&gt;Standardized credential providers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/migrate-credential-providers.html"&gt;AWS SDK for JavaScript v3 credential providers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html"&gt;IAM best practices&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>The Art of Decision-Making - Why Engineers Should Stop Being Imposters</title><link>https://www.yopa.page/blog/2023-06-14-the-art-of-decision-making.html</link><pubDate>Wed, 14 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-06-14-the-art-of-decision-making.html</guid><description>
&lt;p&gt;&lt;em&gt;Don&amp;rsquo;t just be an engineer, be an imposter! Wait, what? That&amp;rsquo;s right, folks. In the world of software development, it seems like many engineers have taken on the role of the indecisive imposter, desperately seeking approval for even the tiniest of decisions. But hey, who needs efficiency and progress when we can waste company resources and time, am I right?&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="analysis-paralysis-the-rabbit-hole-of-never-ending-decisions"&gt;
&lt;a href="#analysis-paralysis-the-rabbit-hole-of-never-ending-decisions" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Analysis Paralysis: The Rabbit Hole of Never-Ending Decisions
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s set the stage. You&amp;rsquo;re an engineer working on a complex software project. You&amp;rsquo;re faced with a decision, whether it&amp;rsquo;s choosing the right library, deciding on an architectural approach, or simply picking the color of a button. But instead of taking charge and making a call, you find yourself stuck in the abyss of analysis paralysis.&lt;/p&gt;
&lt;p&gt;Analysis paralysis, a term coined for this very situation, refers to the state of overthinking and overanalyzing to the point where a decision becomes nearly impossible. It&amp;rsquo;s like being trapped in a never-ending loop of &amp;ldquo;What if?&amp;rdquo; and &amp;ldquo;But what about this?&amp;rdquo;&lt;/p&gt;
&lt;h2 id="the-need-for-decisiveness-taking-responsibility"&gt;
&lt;a href="#the-need-for-decisiveness-taking-responsibility" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Need for Decisiveness: Taking Responsibility
&lt;/h2&gt;
&lt;p&gt;As engineers, we must embrace the critical mindset of decision-making. We need to acknowledge that our ability to make informed choices is what drives progress and success. It&amp;rsquo;s not about seeking approval or waiting for someone else to make the call. No, it&amp;rsquo;s about taking responsibility for our decisions and standing by them.&lt;/p&gt;
&lt;p&gt;Thomas Edison, the inventor of the light bulb, once said, &amp;ldquo;I have not failed. I&amp;rsquo;ve just found 10,000 ways that won&amp;rsquo;t work.&amp;rdquo; Edison understood that making decisions, even if they turn out to be wrong, is a crucial part of the journey to success. So why are we so afraid to make decisions?&lt;/p&gt;
&lt;h2 id="unnecessary-meetings-the-signature-move-of-an-imposter"&gt;
&lt;a href="#unnecessary-meetings-the-signature-move-of-an-imposter" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Unnecessary Meetings: The Signature Move of an Imposter
&lt;/h2&gt;
&lt;p&gt;Picture this: you&amp;rsquo;re in a meeting room filled with your fellow engineers, managers, and stakeholders. The topic of discussion? The color of a button. Yes, you heard that right—a button. The room is buzzing with opinions, ideas, and a whole lot of wasted time. And why? Because you, the indecisive engineer, wanted approval from the authority figures.&lt;/p&gt;
&lt;p&gt;Albert Einstein, the brilliant physicist, once remarked, &amp;ldquo;The measure of intelligence is the ability to change.&amp;rdquo; Yet, in this scenario, it seems like intelligence has taken a backseat. By seeking approval for the minutest of decisions, engineers inadvertently become imposters, wasting valuable company resources.&lt;/p&gt;
&lt;h2 id="the-imposter-syndrome-a-good-sign"&gt;
&lt;a href="#the-imposter-syndrome-a-good-sign" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Imposter Syndrome: A Good Sign?
&lt;/h2&gt;
&lt;p&gt;Being an imposter in the software development field might not be what you think. It&amp;rsquo;s not about pretending to know things you don&amp;rsquo;t. No, it&amp;rsquo;s about shying away from the responsibility of making decisions and hiding behind the need for approval. The imposter engineer thrives on unnecessary meetings and the collective decision-making process, all while draining the company&amp;rsquo;s resources.&lt;/p&gt;
&lt;p&gt;So, the next time you find yourself hesitating to make a decision, take a moment to reflect. Are you falling into the imposter syndrome trap? Are you wasting time, money, and the potential of your team? Remember the wise words of Mark Twain: &amp;ldquo;Twenty years from now, you will be more disappointed by the things you didn&amp;rsquo;t do than by the ones you did.&amp;rdquo;&lt;/p&gt;
&lt;h2 id="embrace-the-decisive-engineer-within"&gt;
&lt;a href="#embrace-the-decisive-engineer-within" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Embrace the Decisive Engineer Within
&lt;/h2&gt;
&lt;p&gt;In a world that demands progress and innovation, engineers cannot afford to be imposters. It&amp;rsquo;s time to break free from the analysis paralysis rabbit hole and take responsibility for our decisions. Let&amp;rsquo;s embrace the critical mindset that defines our profession and make the choices that propel us forward.&lt;/p&gt;
&lt;p&gt;As engineers, we have the power to shape the future. So, let&amp;rsquo;s make decisions, take risks, and learn from our mistakes. After all, as the legendary Steve Jobs once said, &amp;ldquo;The people who are crazy enough to think they can change the world are the ones who do.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;Remember, the imposter syndrome might be a good plot twist for a game, but in the real world of software development, it&amp;rsquo;s time to leave that role behind. Be decisive, be responsible, and be the engineer you were meant to be.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;em&gt;Disclaimer: This article is intended for entertainment purposes. (or is it?)&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Cheers! 🍺&lt;/p&gt;</description></item><item><title>Sign API Gateway Requests with SigV4 and AWS SDK for JavaScript v3</title><link>https://www.yopa.page/blog/2026-08-01-sign-api-gateway-requests-with-javascript-v3.html</link><pubDate>Tue, 13 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-sign-api-gateway-requests-with-javascript-v3.html</guid><description>
&lt;p&gt;Signature Version 4 proves that an AWS request was signed by credentials authorized to perform an action. For API Gateway, it is useful when callers already have AWS identities: internal workloads, operators using federation, or CI using OIDC. It is not a replacement for end-user authentication in a public browser application.&lt;/p&gt;
&lt;p&gt;The older pattern combined the end-of-support AWS SDK for JavaScript v2, a third-party signer, and access keys in source code. The current pattern uses modular v3 packages and the default credential provider chain.&lt;/p&gt;
&lt;h2 id="configure-the-api-and-caller"&gt;
&lt;a href="#configure-the-api-and-caller" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Configure the API and caller
&lt;/h2&gt;
&lt;p&gt;Set the API Gateway route or method authorization type to &lt;code&gt;AWS_IAM&lt;/code&gt;. Grant the caller &lt;code&gt;execute-api:Invoke&lt;/code&gt; only on the required API, stage, method, and resource path. The API&amp;rsquo;s resource policy and network policy may impose additional limits.&lt;/p&gt;
&lt;p&gt;For local development, log in with IAM Identity Center. On Lambda, ECS, or EC2, attach a role. The same code can use both because it does not contain keys.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm install @aws-sdk/signature-v4 @aws-sdk/protocol-http &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; @aws-sdk/credential-provider-node @aws-crypto/sha256-js
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="sign-and-send"&gt;
&lt;a href="#sign-and-send" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Sign and send
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SignatureV4&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@aws-sdk/signature-v4&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;HttpRequest&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@aws-sdk/protocol-http&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;defaultProvider&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@aws-sdk/credential-provider-node&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Sha256&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@aws-crypto/sha256-js&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;region&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;us-east-1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;URL&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;https://abc123.execute-api.us-east-1.amazonaws.com/prod/orders&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;orderId&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;123&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;SignatureV4&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;execute-api&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;credentials&lt;/span&gt;: &lt;span class="kt"&gt;defaultProvider&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;sha256&lt;/span&gt;: &lt;span class="kt"&gt;Sha256&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;HttpRequest&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;protocol&lt;/span&gt;: &lt;span class="kt"&gt;url.protocol&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;hostname&lt;/span&gt;: &lt;span class="kt"&gt;url.hostname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;POST&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pathname&lt;/span&gt;&lt;span class="si"&gt;}${&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;search&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;host&lt;/span&gt;: &lt;span class="kt"&gt;url.hostname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;content-type&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;application/json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;signer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;method&lt;/span&gt;: &lt;span class="kt"&gt;signed.method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;: &lt;span class="kt"&gt;signed.headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`API returned &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;status&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The exact body, path, query string, host, and signed headers sent over the network must match what was signed. Do not mutate JSON or add a proxy path afterward. Temporary credentials require the session token; the provider supplies it to the signer.&lt;/p&gt;
&lt;h2 id="debugging-without-exposing-secrets"&gt;
&lt;a href="#debugging-without-exposing-secrets" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Debugging without exposing secrets
&lt;/h2&gt;
&lt;p&gt;On a &lt;code&gt;403&lt;/code&gt;, distinguish an API authorization failure from &lt;code&gt;SignatureDoesNotMatch&lt;/code&gt;. Confirm the account and ARN with STS, Region, service name &lt;code&gt;execute-api&lt;/code&gt;, deployed stage/path, clock synchronization, resource ARN, and API resource policy. Log request IDs and non-sensitive canonical inputs, never the Authorization header, session token, or credentials.&lt;/p&gt;
&lt;h2 id="alternatives-and-tradeoffs"&gt;
&lt;a href="#alternatives-and-tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Alternatives and tradeoffs
&lt;/h2&gt;
&lt;p&gt;SigV4 gives strong workload identity and IAM policy control, but browsers cannot safely hold AWS credentials. Use Cognito/OIDC JWT authorization or another application identity layer for end users. For service-to-service calls outside AWS identity boundaries, OAuth 2.0 may provide a better contract. API keys in API Gateway identify usage-plan consumers; they are not authorization by themselves.&lt;/p&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Remove SDK v2, &lt;code&gt;aws4&lt;/code&gt;, and explicit access keys.&lt;/li&gt;
&lt;li&gt;Enable &lt;code&gt;AWS_IAM&lt;/code&gt; only for callers that can obtain AWS credentials safely.&lt;/li&gt;
&lt;li&gt;Use Identity Center, workload roles, or OIDC and the provider chain.&lt;/li&gt;
&lt;li&gt;Scope &lt;code&gt;execute-api:Invoke&lt;/code&gt; to the necessary route.&lt;/li&gt;
&lt;li&gt;Test GET, query encoding, and non-empty body requests.&lt;/li&gt;
&lt;li&gt;Test expired credentials, wrong Region, wrong route, and denied IAM permissions.&lt;/li&gt;
&lt;li&gt;Verify CloudTrail/API logs without recording signed secrets.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="canonicalization-cases-worth-testing"&gt;
&lt;a href="#canonicalization-cases-worth-testing" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Canonicalization cases worth testing
&lt;/h2&gt;
&lt;p&gt;SigV4 bugs often hide in inputs that a happy-path GET does not cover. Test repeated query keys, spaces and Unicode, an empty body, a JSON body, and a path containing percent-encoded characters. Build the final serialized body once, sign those bytes, and send those same bytes. If a library or proxy normalizes the path or host after signing, the service calculates a different canonical request.&lt;/p&gt;
&lt;p&gt;Credential-provider behavior deserves its own test boundary. In unit tests, inject a fixed fake provider into the signer; never depend on a developer profile. In an integration environment, assume a dedicated low-privilege role, invoke an allowed route, and confirm a neighboring method and route are denied. This proves both the positive path and the resource ARN scope.&lt;/p&gt;
&lt;p&gt;For cross-account invocation, the API owner may need a resource policy in addition to the caller&amp;rsquo;s identity policy. For private APIs, endpoint and VPC policies add more policy layers. Diagnose each layer independently rather than broadening all of them until the call succeeds. A temporary &lt;code&gt;execute-api:*&lt;/code&gt; grant can conceal an incorrect route ARN and is easy to leave behind.&lt;/p&gt;
&lt;p&gt;Finally, retry only operations that are safe to retry. Signature or authorization errors will not recover with exponential backoff. For mutating APIs, use an application idempotency key so a network retry cannot create a duplicate business action.&lt;/p&gt;
&lt;p&gt;Verified on &lt;strong&gt;2026-08-01&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="official-sources"&gt;
&lt;a href="#official-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Official sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/permissions.html"&gt;Control access to an API with IAM permissions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_sigv.html"&gt;Signing AWS API requests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/migrate-credential-providers.html"&gt;AWS SDK for JavaScript v3 credential providers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Designing Reliable Work Queues with Amazon SQS</title><link>https://www.yopa.page/blog/2023-06-12-aws-sqs-queue.html</link><pubDate>Mon, 12 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-06-12-aws-sqs-queue.html</guid><description>
&lt;p&gt;Amazon SQS decouples producers from consumers, absorbs bursts, and retains work during temporary consumer failures. Reliability still depends on the consumer design. SQS does not by itself guarantee that a business operation happens exactly once.&lt;/p&gt;
&lt;h2 id="choose-standard-or-fifo"&gt;
&lt;a href="#choose-standard-or-fifo" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Choose Standard or FIFO
&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Standard queues&lt;/strong&gt; provide very high throughput, at-least-once delivery, and best-effort ordering. A message can be delivered more than once, so consumers must be idempotent.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;FIFO queues&lt;/strong&gt; preserve order within a message group and provide SQS deduplication semantics. They are useful when order is a real invariant, but message-group design affects concurrency. FIFO does not make an external database charge or API call exactly once; the consumer still needs an idempotency key or transactional boundary.&lt;/p&gt;
&lt;h2 id="the-processing-contract"&gt;
&lt;a href="#the-processing-contract" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The processing contract
&lt;/h2&gt;
&lt;p&gt;Receiving a message makes it temporarily invisible. Delete it only after the business operation commits. Set the visibility timeout longer than normal processing plus a margin, and extend it with &lt;code&gt;ChangeMessageVisibility&lt;/code&gt; for legitimately long work. If it is too short, another worker can process the same message concurrently; if too long, recovery from a crashed worker is delayed.&lt;/p&gt;
&lt;p&gt;Use an immutable message ID or business idempotency key. Store the result atomically where possible, or make the target API accept the key. Retries must use backoff and distinguish transient errors from permanent invalid input.&lt;/p&gt;
&lt;p&gt;Configure a dead-letter queue with a deliberate &lt;code&gt;maxReceiveCount&lt;/code&gt;. A DLQ is not a trash can: alarm on it, preserve enough context to diagnose safely, and define a reviewed redrive procedure. Redriving without fixing the cause can repeat side effects or create another outage.&lt;/p&gt;
&lt;h2 id="efficient-consumers"&gt;
&lt;a href="#efficient-consumers" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Efficient consumers
&lt;/h2&gt;
&lt;p&gt;Use long polling to reduce empty receives and batch receive/delete operations where failure handling remains correct. Scale consumers on queue depth and age of oldest message, not CPU alone. Cap concurrency to protect databases and downstream APIs. For Lambda event source mappings, understand partial batch responses so one failed record does not force successful records through unnecessary retries.&lt;/p&gt;
&lt;p&gt;Encrypt queues when required, use TLS, and scope producer/consumer IAM permissions to specific queues and actions. Avoid sensitive payloads when a reference to encrypted storage is sufficient. Remember retention and DLQ retention in privacy and deletion policies.&lt;/p&gt;
&lt;h2 id="order-example"&gt;
&lt;a href="#order-example" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Order example
&lt;/h2&gt;
&lt;p&gt;Do not represent an entire order workflow as unrelated messages that can independently charge, reserve inventory, and notify without coordination. Publish a durable order identifier, make each step idempotent, record state transitions, and use a workflow or saga when compensation and ordering matter. SQS transports work; it is not the system of record.&lt;/p&gt;
&lt;h2 id="verification-checklist"&gt;
&lt;a href="#verification-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Verification checklist
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Deliver the same message twice and confirm one business effect.&lt;/li&gt;
&lt;li&gt;Kill a worker after commit but before delete.&lt;/li&gt;
&lt;li&gt;Run longer than the visibility timeout and test extension.&lt;/li&gt;
&lt;li&gt;Trigger transient retries and poison-message DLQ routing.&lt;/li&gt;
&lt;li&gt;Test partial batch failure and redrive.&lt;/li&gt;
&lt;li&gt;Alarm on age, visible/not-visible count, DLQ depth, consumer errors, and throttling.&lt;/li&gt;
&lt;li&gt;Load test with downstream concurrency limits in place.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Official documentation reviewed on &lt;strong&gt;2026-08-01&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/standard-queues-at-least-once-delivery.html"&gt;Amazon SQS delivery semantics&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-visibility-timeout.html"&gt;Visibility timeout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-dead-letter-queues.html"&gt;Dead-letter queues&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/services-sqs-errorhandling.html"&gt;Lambda partial batch responses&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Secure Direct Uploads with Amazon S3 Presigned URLs</title><link>https://www.yopa.page/blog/2023-06-11-advantages-of-using-presigned-urls.html</link><pubDate>Sun, 11 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-06-11-advantages-of-using-presigned-urls.html</guid><description>
&lt;p&gt;An S3 presigned URL lets a client perform one signed S3 operation without receiving AWS credentials. Direct upload avoids sending large files through the application server, but a presigned URL is not automatically safe: anyone who obtains it can use it until it expires or the signing credentials stop being valid.&lt;/p&gt;
&lt;h2 id="a-safer-upload-flow"&gt;
&lt;a href="#a-safer-upload-flow" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A safer upload flow
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;The authenticated client asks the application for permission to upload metadata such as file name, size, and media type.&lt;/li&gt;
&lt;li&gt;The application authorizes the user and generates an unpredictable object key inside that user&amp;rsquo;s allowed prefix.&lt;/li&gt;
&lt;li&gt;The application returns a short-lived presigned &lt;code&gt;PUT&lt;/code&gt; URL or presigned POST policy.&lt;/li&gt;
&lt;li&gt;The client uploads directly to S3.&lt;/li&gt;
&lt;li&gt;An S3 event or explicit completion request starts validation, malware scanning, metadata extraction, and publication. Keep untrusted uploads in a quarantine prefix or bucket until this finishes.&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;boto3&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;s3&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;s3&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate_presigned_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;put_object&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;Params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Bucket&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;private-upload-bucket&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;Key&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;incoming/user-123/7a4f...&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;ContentType&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;image/png&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;ExpiresIn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The client must send the same signed content type. For stricter size constraints, use a presigned POST policy with &lt;code&gt;content-length-range&lt;/code&gt;, or enforce limits through another trusted boundary. For large uploads, use multipart upload and abort incomplete uploads with an S3 lifecycle rule.&lt;/p&gt;
&lt;h2 id="security-boundaries"&gt;
&lt;a href="#security-boundaries" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Security boundaries
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Use the shortest practical expiration. A URL signed with temporary credentials cannot outlive those credentials.&lt;/li&gt;
&lt;li&gt;Treat the URL as a bearer secret: do not put it in analytics, chat, referer-bearing pages, or durable logs.&lt;/li&gt;
&lt;li&gt;Give the signing role access only to the required bucket, operation, and key prefix.&lt;/li&gt;
&lt;li&gt;Keep Block Public Access enabled; a presigned URL does not require public objects.&lt;/li&gt;
&lt;li&gt;Consider checksums, allowed content type, unique keys, server-side encryption, retention, and overwrite behavior.&lt;/li&gt;
&lt;li&gt;Configure CORS only for trusted origins and necessary methods/headers. CORS is a browser control, not authorization.&lt;/li&gt;
&lt;li&gt;Do not make an upload public until server-side validation succeeds.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Revocation is limited. Removing the object does not invalidate an upload URL, and a generated URL has no independent revoke button. You can revoke or restrict the credentials/policy used to sign it, but that affects other requests. Short expiry and narrowly scoped signing permissions are the practical controls.&lt;/p&gt;
&lt;h2 id="when-server-side-upload-is-better"&gt;
&lt;a href="#when-server-side-upload-is-better" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
When server-side upload is better
&lt;/h2&gt;
&lt;p&gt;Proxy through the application when it must inspect or transform every byte before storage, the client cannot talk to S3, or centralized rate limiting outweighs bandwidth cost. Presigned direct upload is preferable for large trusted workflows, but it moves validation to an asynchronous boundary that must be designed explicitly.&lt;/p&gt;
&lt;p&gt;Migration verification: confirm private bucket policy, signing-role scope, expiry, wrong-user denial, size/type rejection, duplicate key behavior, incomplete multipart cleanup, scanner failure, and secret-free logging.&lt;/p&gt;
&lt;p&gt;Official documentation reviewed on &lt;strong&gt;2026-08-01&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html"&gt;Download and upload objects with presigned URLs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/PresignedUrlUploadObject.html"&gt;Presigned URL upload&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/cors.html"&gt;S3 CORS&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Choosing When to Use Amazon API Gateway</title><link>https://www.yopa.page/blog/2023-06-10-why-you-need-to-bother-to-use-aws-api-gateway.html</link><pubDate>Sat, 10 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-06-10-why-you-need-to-bother-to-use-aws-api-gateway.html</guid><description>
&lt;p&gt;Amazon API Gateway is a managed front door for HTTP, REST, and WebSocket APIs. It can provide routing, authorization, throttling, metrics, stages, and integrations without requiring a team to operate an API proxy fleet. That does not mean every service needs it.&lt;/p&gt;
&lt;h2 id="start-with-the-requirement"&gt;
&lt;a href="#start-with-the-requirement" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Start with the requirement
&lt;/h2&gt;
&lt;p&gt;Choose &lt;strong&gt;API Gateway HTTP API&lt;/strong&gt; for a relatively simple Lambda or HTTP proxy API when JWT/OIDC authorization, IAM authorization, custom domains, CORS, and lower cost are important. Choose &lt;strong&gt;API Gateway REST API&lt;/strong&gt; when a required feature is available only there, such as usage plans/API keys, request validation or transformation capabilities, or particular caching and private API patterns. Check the official feature comparison because the two products are not interchangeable.&lt;/p&gt;
&lt;p&gt;Consider alternatives:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Lambda Function URL:&lt;/strong&gt; one Lambda, simple ingress, and no need for API Gateway&amp;rsquo;s broader management features.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Application Load Balancer:&lt;/strong&gt; existing container or instance services, long-lived connections, or load-balancer-native routing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS AppSync:&lt;/strong&gt; GraphQL and managed real-time/data-source integration requirements.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Direct service-to-service integration:&lt;/strong&gt; asynchronous EventBridge, SNS, or SQS may be a better boundary than a synchronous API.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="security-is-configuration-not-a-default-outcome"&gt;
&lt;a href="#security-is-configuration-not-a-default-outcome" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Security is configuration, not a default outcome
&lt;/h2&gt;
&lt;p&gt;Pick authorization for the caller: IAM/SigV4 for AWS workloads, JWT authorizers for OIDC identities, Cognito user pools where appropriate, or a Lambda authorizer for genuinely custom logic. An API key identifies a usage-plan consumer; it is not authentication by itself.&lt;/p&gt;
&lt;p&gt;Apply throttling and quotas intentionally, validate input at the application boundary, restrict backend roles, and avoid logging tokens or sensitive request bodies. A private API or resource policy can reduce network exposure, but authorization remains necessary.&lt;/p&gt;
&lt;h2 id="operations-and-cost"&gt;
&lt;a href="#operations-and-cost" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Operations and cost
&lt;/h2&gt;
&lt;p&gt;API Gateway scales the managed entry point, not the backend. Set Lambda concurrency, database connection limits, retries, and timeouts so a traffic spike does not move the failure downstream. Emit access logs with request IDs, metrics and alarms for latency, 4xx/5xx responses, integration errors, and throttles. Sample tracing where it answers an operational question.&lt;/p&gt;
&lt;p&gt;Pricing differs by API type, Region, request volume, payload and data transfer. Compare the live pricing page with ALB or direct alternatives for the expected traffic shape rather than assuming pay-per-request is always cheaper.&lt;/p&gt;
&lt;h2 id="a-small-decision-checklist"&gt;
&lt;a href="#a-small-decision-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A small decision checklist
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Identify protocol, caller identity, traffic shape, payload size, and latency target.&lt;/li&gt;
&lt;li&gt;List the required authorization, transformation, caching, WebSocket, and private-network features.&lt;/li&gt;
&lt;li&gt;Pick HTTP API by default when it meets the list; use REST API only for a required REST-specific capability.&lt;/li&gt;
&lt;li&gt;Test throttling, authorization denial, backend timeout, malformed input, and deployment rollback.&lt;/li&gt;
&lt;li&gt;Verify logs and alarms without collecting secrets.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Official documentation reviewed on &lt;strong&gt;2026-08-01&lt;/strong&gt;:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html"&gt;Choose between HTTP APIs and REST APIs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/security.html"&gt;API Gateway security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/limits.html"&gt;API Gateway quotas&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Getting to Yes - Negotiating Agreement Without Giving In by Roger Fisher</title><link>https://www.yopa.page/blog/2023-06-09-getting-to-yes-negotiating-agreement-without-giving-in.html</link><pubDate>Fri, 09 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-06-09-getting-to-yes-negotiating-agreement-without-giving-in.html</guid><description>
&lt;h2 id="a-key-resource-for-persuasive-negotiation-in-software-engineering"&gt;
&lt;a href="#a-key-resource-for-persuasive-negotiation-in-software-engineering" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A Key Resource for Persuasive Negotiation in Software Engineering
&lt;/h2&gt;
&lt;p&gt;&amp;ldquo;Getting to Yes: Negotiating Agreement Without Giving In&amp;rdquo; by Roger Fisher, William Ury, and Bruce Patton is a seminal text that offers a roadmap for negotiating mutually beneficial agreements in any situation. Its principles are particularly beneficial when trying to persuade others in the software engineering world, especially in discussions around project scope, resources, and timelines.&lt;/p&gt;
&lt;h3 id="four-principles-of-effective-negotiation"&gt;
&lt;a href="#four-principles-of-effective-negotiation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Four Principles of Effective Negotiation
&lt;/h3&gt;
&lt;p&gt;The authors outline four principles for effective negotiation, which should be observed at each stage of the negotiation process, from analyzing the situation, planning responses, to discussing the problem to find a common solution.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Separate the People from the Problem&lt;/strong&gt;: People often become personally involved with the issues and their side&amp;rsquo;s positions. The authors stress the importance of separating the people from the issues to avoid damaging relationships and to get a clearer view of the substantive problem【14†source】.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Focus on Interests, Not Positions&lt;/strong&gt;: Rather than bargaining from separate opening positions, which often leads to inefficient and unfair agreements, Fisher and Ury suggest focusing on the underlying interests of all parties involved.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Generate a Variety of Options Before Settling on an Agreement&lt;/strong&gt;: Instead of rushing to find a single answer, the authors advise generating multiple options to solve the problem. This allows for a more comprehensive exploration of possible solutions that could satisfy all parties.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Insist That the Agreement Be Based on Objective Criteria&lt;/strong&gt;: To ensure fairness and durability, the agreement should be grounded on objective criteria rather than personal bias or power dynamics.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Although I was unable to find explicit real-world examples of these principles in action within the software engineering context due to time constraints and technical issues, they are widely applicable in a range of scenarios. For instance, when negotiating project resources, one might focus on the interests of delivering a quality product on time (a shared interest) rather than sticking to initial positions (e.g., I need X amount of resources). Multiple options for resource allocation could be explored, and the final agreement should be based on objective criteria such as project requirements and available resources.&lt;/p&gt;
&lt;p&gt;These principles can serve as a guide for software engineers and project managers, helping to transform negotiations from battles into collaborative problem-solving sessions, fostering better relationships, and leading to more efficient and satisfactory outcomes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Never argue with stupid people. They will drag you down to their level and then beat you with experience.&amp;rdquo; - Mark Twain&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Cheers! 🍺&lt;/p&gt;</description></item><item><title>Export IAM Identity Center Credentials Only When a Tool Requires Them</title><link>https://www.yopa.page/blog/2026-08-01-export-iam-identity-center-credentials-safely.html</link><pubDate>Thu, 08 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-export-iam-identity-center-credentials-safely.html</guid><description>
&lt;p&gt;Most AWS-aware applications should receive a profile name and use the standard credential provider chain. Occasionally, a legacy tool understands only &lt;code&gt;AWS_ACCESS_KEY_ID&lt;/code&gt;, &lt;code&gt;AWS_SECRET_ACCESS_KEY&lt;/code&gt;, and &lt;code&gt;AWS_SESSION_TOKEN&lt;/code&gt;. That is an interoperability exception, not a reason to parse &lt;code&gt;~/.aws/sso/cache&lt;/code&gt; or call the IAM Identity Center API yourself.&lt;/p&gt;
&lt;h2 id="why-the-old-approach-changed"&gt;
&lt;a href="#why-the-old-approach-changed" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why the old approach changed
&lt;/h2&gt;
&lt;p&gt;An Identity Center cache can hold several sessions. Selecting the first JSON file may choose the wrong identity, and the cache schema is not a supported integration contract. Passing an access token on a shell command line can also expose it through logs or process inspection. The AWS CLI already knows which cached session belongs to a profile and how to exchange it for role credentials.&lt;/p&gt;
&lt;h2 id="prefer-direct-profile-support"&gt;
&lt;a href="#prefer-direct-profile-support" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Prefer direct profile support
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws configure sso --profile engineering-dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws sso login --profile engineering-dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;AWS_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;engineering-dev aws sts get-caller-identity
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;If the target tool supports AWS profiles, stop here:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;AWS_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;engineering-dev your-tool
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;For SDK code, omit explicit keys and use the default provider chain. For CI or hosted workloads, use OIDC or a workload role instead of a human Identity Center session.&lt;/p&gt;
&lt;h2 id="supported-export-for-a-legacy-process"&gt;
&lt;a href="#supported-export-for-a-legacy-process" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Supported export for a legacy process
&lt;/h2&gt;
&lt;p&gt;AWS CLI v2 provides &lt;code&gt;aws configure export-credentials&lt;/code&gt;. Verify your CLI exposes the command, then export only into the current shell immediately before launching the tool:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws sso login --profile engineering-dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws sts get-caller-identity --profile engineering-dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;eval&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;aws configure export-credentials --profile engineering-dev --format env&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;your-legacy-tool
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;unset&lt;/span&gt; AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;eval&lt;/code&gt; executes generated shell text, so use it only with the trusted AWS CLI binary, a fixed or allowlisted profile, and the &lt;code&gt;env&lt;/code&gt; format. Do not accept the profile value from untrusted input. Do not echo the output or put it in shell history, a ticket, a &lt;code&gt;.env&lt;/code&gt; file, or CI variables.&lt;/p&gt;
&lt;p&gt;For a child process, prefer &lt;code&gt;env-no-export&lt;/code&gt; output and parse it without logging, or configure a &lt;code&gt;credential_process&lt;/code&gt; when the consumer supports the shared AWS config contract. A wrapper should verify the expected account and fail closed before launching the target.&lt;/p&gt;
&lt;h2 id="tradeoffs-and-lifetime"&gt;
&lt;a href="#tradeoffs-and-lifetime" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Tradeoffs and lifetime
&lt;/h2&gt;
&lt;p&gt;Environment variables are widely compatible, but every child process inherits them and some diagnostic tools capture environments. A profile provider has a smaller exposure surface and can refresh credentials. Exported role credentials expire no later than their source session, so a long-running process must handle expiration; repeatedly scraping the cache does not solve refresh safely.&lt;/p&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Confirm the tool truly lacks profile, &lt;code&gt;credential_process&lt;/code&gt;, web identity, or workload-role support.&lt;/li&gt;
&lt;li&gt;Upgrade AWS CLI v2 and configure a named Identity Center profile.&lt;/li&gt;
&lt;li&gt;Verify account and ARN with STS.&lt;/li&gt;
&lt;li&gt;Export credentials only for the child process or current short-lived shell.&lt;/li&gt;
&lt;li&gt;Test expiration and make failures explicit.&lt;/li&gt;
&lt;li&gt;Remove copied keys and custom SSO-cache parsing.&lt;/li&gt;
&lt;li&gt;Record the legacy exception and an owner/date for eliminating it.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="a-tighter-wrapper-pattern"&gt;
&lt;a href="#a-tighter-wrapper-pattern" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A tighter wrapper pattern
&lt;/h2&gt;
&lt;p&gt;When the tool is invoked from a script, keep exported variables out of the parent shell. Ask the CLI for &lt;code&gt;env-no-export&lt;/code&gt;, parse only the three expected names, construct a child environment, start the process without a shell, and discard the map afterward. Reject duplicate or unexpected keys. This is more code than &lt;code&gt;eval&lt;/code&gt;, but it prevents later commands in an interactive shell from inheriting the credentials and avoids shell expansion.&lt;/p&gt;
&lt;p&gt;Before launch, call STS using the same profile and compare the returned account with a configured account ID. The wrapper should also set a maximum runtime shorter than the remaining session lifetime when the legacy tool cannot refresh credentials. A halfway-completed destructive operation is a poor way to discover expiry.&lt;/p&gt;
&lt;p&gt;Audit without collecting secrets: record the tool version, profile name, resolved account and role ARN, start/end time, exit status, and AWS request IDs emitted by the tool. Never record the export output. On incident response, end the Identity Center session and revoke or narrow the permission set as appropriate; deleting a local cache file alone does not undo actions already authorized.&lt;/p&gt;
&lt;p&gt;This workaround is best for an interactive migration window. If it becomes a permanent daemon, redesign it around a provider-aware SDK, &lt;code&gt;credential_process&lt;/code&gt;, web identity, or a managed AWS compute role.&lt;/p&gt;
&lt;p&gt;Verified on &lt;strong&gt;2026-08-01&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="official-sources"&gt;
&lt;a href="#official-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Official sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/reference/configure/export-credentials.html"&gt;&lt;code&gt;aws configure export-credentials&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html"&gt;IAM Identity Center authentication in AWS CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/sdkref/latest/guide/feature-process-credentials.html"&gt;Sourcing credentials with an external process&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>A Summary of Influence - The Psychology of Persuasion</title><link>https://www.yopa.page/blog/2023-06-07-a-summary-of-influence-the-psychology-of-persuasion.html</link><pubDate>Wed, 07 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-06-07-a-summary-of-influence-the-psychology-of-persuasion.html</guid><description>
&lt;p&gt;In his seminal book, &lt;strong&gt;&amp;ldquo;Influence: The Psychology of Persuasion&amp;rdquo;, Robert B. Cialdini&lt;/strong&gt; presents six principles of persuasion that can be leveraged to influence others more effectively. These principles are &lt;strong&gt;Reciprocity&lt;/strong&gt;, &lt;strong&gt;Scarcity&lt;/strong&gt;, &lt;strong&gt;Authority&lt;/strong&gt;, &lt;strong&gt;Consistency&lt;/strong&gt;, &lt;strong&gt;Liking&lt;/strong&gt;, and &lt;strong&gt;Consensus&lt;/strong&gt;. Understanding these principles can help you to persuade others and achieve your goals.&lt;/p&gt;
&lt;h2 id="1-reciprocity"&gt;
&lt;a href="#1-reciprocity" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1. Reciprocity
&lt;/h2&gt;
&lt;p&gt;The principle of Reciprocity states that people are more likely to give back the kind of treatment they have received from others.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;: Imagine you&amp;rsquo;re working on a software project. You need help from a colleague who is an expert in a particular technology. If you&amp;rsquo;ve previously helped them when they needed it, they&amp;rsquo;re much more likely to reciprocate and assist you.&lt;/p&gt;
&lt;h2 id="2-scarcity"&gt;
&lt;a href="#2-scarcity" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2. Scarcity
&lt;/h2&gt;
&lt;p&gt;The principle of Scarcity posits that people value things more when they are rare or difficult to obtain.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;: Suppose your company is considering adopting a new software tool. By highlighting that a discount offer for the tool is about to end soon, you can use the scarcity principle to convince your team to make a quicker decision.&lt;/p&gt;
&lt;h2 id="3-authority"&gt;
&lt;a href="#3-authority" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
3. Authority
&lt;/h2&gt;
&lt;p&gt;The Authority principle suggests that people tend to follow the lead of credible, knowledgeable experts.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;: If you&amp;rsquo;re a respected tech lead in your team, your recommendations for code quality improvements are likely to be taken more seriously because of your perceived authority.&lt;/p&gt;
&lt;h2 id="4-consistency"&gt;
&lt;a href="#4-consistency" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
4. Consistency
&lt;/h2&gt;
&lt;p&gt;The principle of Consistency holds that people like to be consistent with things they have previously said or done.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;: If a team member has publicly expressed commitment to improving code quality, they&amp;rsquo;re more likely to follow through on actions that align with this commitment, such as adopting new coding standards or practices.&lt;/p&gt;
&lt;h2 id="5-liking"&gt;
&lt;a href="#5-liking" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
5. Liking
&lt;/h2&gt;
&lt;p&gt;The Liking principle argues that people are more likely to be persuaded by people they like.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;: If you have a good relationship with your project manager, they&amp;rsquo;re more likely to be persuaded by your proposal to adopt a new project management tool.&lt;/p&gt;
&lt;h2 id="6-consensus"&gt;
&lt;a href="#6-consensus" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
6. Consensus
&lt;/h2&gt;
&lt;p&gt;The Consensus principle states that people often look to the actions and behaviors of others to determine their own.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Example&lt;/strong&gt;: If you can demonstrate that multiple successful software companies are using a particular development methodology, your team might be more likely to adopt that methodology as well.&lt;/p&gt;
&lt;h2 id="wrapping-up"&gt;
&lt;a href="#wrapping-up" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Wrapping up.
&lt;/h2&gt;
&lt;p&gt;Understanding and leveraging these six principles can greatly enhance your persuasive abilities. Whether you&amp;rsquo;re negotiating a deal, leading a team, or influencing a decision, these principles can help you achieve your goals.&lt;/p&gt;
&lt;p&gt;Cheers! 🍺&lt;/p&gt;</description></item><item><title>When to Use a Sequence Diagram - A Real Life Example</title><link>https://www.yopa.page/blog/2023-06-06-when-to-use-sequence-diagram.html</link><pubDate>Tue, 06 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-06-06-when-to-use-sequence-diagram.html</guid><description>
&lt;p&gt;In the world of software design and development, diagrammatic representations of various processes and functionalities can prove extremely useful. One such useful tool is the &lt;strong&gt;Sequence Diagram&lt;/strong&gt;. It&amp;rsquo;s a type of interaction diagram found in the Unified Modeling Language (UML). This article sheds light on when to use a sequence diagram, accompanied by a real-life example.&lt;/p&gt;
&lt;h2 id="what-is-a-sequence-diagram"&gt;
&lt;a href="#what-is-a-sequence-diagram" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What is a Sequence Diagram?
&lt;/h2&gt;
&lt;p&gt;A sequence diagram, in the context of UML, represents object collaboration and is used to define event sequences between objects for a certain outcome. It emphasizes the time-ordering of messages and allows developers to understand the architecture of the software system visually.&lt;/p&gt;
&lt;h2 id="when-to-use-a-sequence-diagram"&gt;
&lt;a href="#when-to-use-a-sequence-diagram" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
When to Use a Sequence Diagram?
&lt;/h2&gt;
&lt;p&gt;There are several scenarios where a sequence diagram can be particularly helpful:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Designing complex interactions&lt;/strong&gt;: When you have complex scenarios where several objects interact with each other, sequence diagrams can simplify these interactions by visualizing the message exchange in a sequential order.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Exploring various scenarios&lt;/strong&gt;: Sequence diagrams can represent multiple scenarios within the same model by using alt (alternative), opt (optional), and loop (iteration) fragments.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Realizing use cases&lt;/strong&gt;: Sequence diagrams can be used to map out and realize the details of use cases, showing how a system interacts with actors (users or other systems).&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Understanding and debugging&lt;/strong&gt;: They can help in understanding the existing code or in debugging a system as they provide a clear view of the interactions and the sequence of processes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Protocol or interface modeling&lt;/strong&gt;: Sequence diagrams can depict the details of a protocol or an interface, helping developers to understand how elements within a system communicate.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="real-life-example"&gt;
&lt;a href="#real-life-example" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Real-Life Example
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s consider a real-life example of an online shopping system to understand when and how to use a sequence diagram.&lt;/p&gt;
&lt;p&gt;When a customer places an order in an online shopping system, several objects interact with each other. This interaction involves a &lt;code&gt;Customer&lt;/code&gt;, &lt;code&gt;ShoppingCart&lt;/code&gt;, &lt;code&gt;Payment&lt;/code&gt;, and &lt;code&gt;Order&lt;/code&gt; objects. A sequence diagram can visualize this process, making it easier to understand.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-markdown" data-lang="markdown"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Title: Online Shopping Order Sequence
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; Customer ShoppingCart Payment Order
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | | | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |----1. Add Item to Cart----&amp;gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | | | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |&amp;lt;---2. Confirm Item Added-- |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | | | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |---3. Checkout ----&amp;gt; | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | |&amp;lt;---4. Calculate Total
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | | | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |----5. Pay ----&amp;gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | | |&amp;lt;---6. Process Payment
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | | | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |&amp;lt;---7. Confirm Payment |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | | | |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |----8. Place Order ----&amp;gt; |
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; | | |&amp;lt;---9. Process Order
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; |&amp;lt;--10. Order Confirmation-- |
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this sequence diagram:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;Customer&lt;/code&gt; adds an item to the &lt;code&gt;ShoppingCart&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;ShoppingCart&lt;/code&gt; confirms the item has been added.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Customer&lt;/code&gt; checks out, and the &lt;code&gt;ShoppingCart&lt;/code&gt; calculates the total cost.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Customer&lt;/code&gt; pays for the items.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Payment&lt;/code&gt; processes the payment and confirms it to the &lt;code&gt;Customer&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Customer&lt;/code&gt; places the order.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;Order&lt;/code&gt; processes the order and sends an order confirmation back to the &lt;code&gt;Customer&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="wrapping-up"&gt;
&lt;a href="#wrapping-up" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Wrapping up!
&lt;/h2&gt;
&lt;p&gt;Sequence diagrams are an integral part of UML, representing&lt;/p&gt;
&lt;p&gt;how objects interact in a specific scenario of a use case. They help in simplifying complex interactions, realizing use cases, and understanding the sequence of processes in a system. So, whether you&amp;rsquo;re designing a new system or debugging an existing one, consider using a sequence diagram to visualize and understand the interactions better.&lt;/p&gt;</description></item><item><title>Deploy a Serverless API Safely with AWS CDK v2</title><link>https://www.yopa.page/blog/2026-08-01-deploy-a-serverless-api-with-cdk-v2.html</link><pubDate>Mon, 05 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-deploy-a-serverless-api-with-cdk-v2.html</guid><description>
&lt;p&gt;Older CDK examples often mix &lt;code&gt;@aws-cdk/*&lt;/code&gt; v1 modules with &lt;code&gt;aws-cdk-lib&lt;/code&gt;, target retired Lambda runtimes, and treat deployment credentials as application configuration. A current workflow uses CDK v2 consistently, an actively supported runtime, temporary operator credentials, and a reviewable synthesized template.&lt;/p&gt;
&lt;h2 id="choose-the-api-deliberately"&gt;
&lt;a href="#choose-the-api-deliberately" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Choose the API deliberately
&lt;/h2&gt;
&lt;p&gt;API Gateway HTTP APIs are usually the lower-cost, lower-complexity choice for Lambda proxy APIs. REST APIs remain appropriate when you need features specific to that product, such as usage plans or certain request transformations. A Lambda Function URL may fit a very small service with simple authorization, while an Application Load Balancer is often a better match for an existing container service.&lt;/p&gt;
&lt;p&gt;The following example keeps REST API because it mirrors the older article, but the choice should be explicit.&lt;/p&gt;
&lt;h2 id="cdk-v2-stack"&gt;
&lt;a href="#cdk-v2-stack" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
CDK v2 stack
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mkdir current-api &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd&lt;/span&gt; current-api
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npx aws-cdk@latest init app --language typescript
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm install aws-cdk-lib constructs
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Stack&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;RemovalPolicy&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aws-cdk-lib&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Construct&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;constructs&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Runtime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Code&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aws-cdk-lib/aws-lambda&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;RestApi&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;LambdaIntegration&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aws-cdk-lib/aws-apigateway&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;BlockPublicAccess&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;aws-cdk-lib/aws-s3&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;ApiStack&lt;/span&gt; &lt;span class="kr"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Stack&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;: &lt;span class="kt"&gt;Construct&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props?&lt;/span&gt;: &lt;span class="kt"&gt;StackProps&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;super&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scope&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;props&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Bucket&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Data&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;blockPublicAccess&lt;/span&gt;: &lt;span class="kt"&gt;BlockPublicAccess.BLOCK_ALL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;enforceSSL&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;removalPolicy&lt;/span&gt;: &lt;span class="kt"&gt;RemovalPolicy.RETAIN&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;handler&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Handler&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;runtime&lt;/span&gt;: &lt;span class="kt"&gt;Runtime.NODEJS_22_X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;index.handler&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;code&lt;/span&gt;: &lt;span class="kt"&gt;Code.fromAsset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;lambda&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;environment&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;BUCKET_NAME&lt;/span&gt;: &lt;span class="kt"&gt;bucket.bucketName&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;grantRead&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;api&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;RestApi&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Api&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;api&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;root&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;addMethod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;GET&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;LambdaIntegration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;grantRead&lt;/code&gt; creates a scoped identity policy for the function. It does not mean CDK inferred every permission your code might need. Review the synthesized IAM statements and avoid wildcard grants.&lt;/p&gt;
&lt;h2 id="authenticate-inspect-deploy"&gt;
&lt;a href="#authenticate-inspect-deploy" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Authenticate, inspect, deploy
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws sso login --profile engineering-dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;AWS_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;engineering-dev aws sts get-caller-identity
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;AWS_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;engineering-dev npx cdk bootstrap
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npx cdk synth
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npx cdk diff
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;AWS_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;engineering-dev npx cdk deploy --require-approval broadening
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Bootstrap once per account and Region using an authorized deployment role. Commit &lt;code&gt;cdk.json&lt;/code&gt;, source, lockfile, and tests—not &lt;code&gt;cdk.out&lt;/code&gt;. Pin the CDK library range and update it deliberately. In CI, use OIDC to assume a deploy role instead of storing access keys.&lt;/p&gt;
&lt;h2 id="production-gaps-the-small-example-does-not-solve"&gt;
&lt;a href="#production-gaps-the-small-example-does-not-solve" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Production gaps the small example does not solve
&lt;/h2&gt;
&lt;p&gt;Add structured logs, alarms, tracing where justified, throttling, an authorization mechanism, input validation, and an explicit data-retention decision. Package dependencies reproducibly. Test the Lambda handler separately and assert the CDK template for security-sensitive properties. Deploy to a non-production account first and keep rollback behavior clear; retained buckets and databases need their own migration and deletion process.&lt;/p&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Remove CDK v1 service packages and import services from &lt;code&gt;aws-cdk-lib&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Import &lt;code&gt;Construct&lt;/code&gt; from &lt;code&gt;constructs&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Select a currently supported Lambda runtime and test the application on it.&lt;/li&gt;
&lt;li&gt;Replace explicit keys with Identity Center locally and OIDC/roles in CI.&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;synth&lt;/code&gt;, tests, security checks, and &lt;code&gt;diff&lt;/code&gt; before deployment.&lt;/li&gt;
&lt;li&gt;Inspect IAM broadening and replacement/destruction in the change set.&lt;/li&gt;
&lt;li&gt;Verify the deployed endpoint, logs, authorization, S3 access, and rollback.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;CDK gives higher-level constructs and reusable TypeScript, but CloudFormation or SAM may be simpler when the infrastructure is small, while Terraform/OpenTofu may fit a multi-provider estate. Choose based on team ownership and lifecycle, not language preference alone.&lt;/p&gt;
&lt;h2 id="test-the-infrastructure-contract"&gt;
&lt;a href="#test-the-infrastructure-contract" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Test the infrastructure contract
&lt;/h2&gt;
&lt;p&gt;CDK assertions catch regressions before CloudFormation receives them. A focused test should verify properties whose accidental removal would change the security or durability boundary: supported runtime, private bucket, TLS enforcement, retention policy, and narrow grants. Do not snapshot the entire synthesized template; broad snapshots are noisy and reviewers often approve them without understanding a changed IAM statement.&lt;/p&gt;
&lt;p&gt;After deployment, invoke the endpoint with an authorized and unauthorized caller, inspect the function logs, put a known object in the bucket, and confirm the function can read it but cannot write or list unrelated buckets. Then exercise rollback from a deliberately broken application version. CloudFormation rollback does not automatically reverse data migrations, so keep stateful changes separate from routine function deployment.&lt;/p&gt;
&lt;p&gt;If the API is internet-facing, add an explicit abuse plan: route throttles, account concurrency protection, payload limits, WAF only where its rules solve a known threat, and cost alarms. If it is internal, verify the resource policy, authorizer, DNS, and network path from the real caller. “The stack deployed” is only a structural check; it does not prove the API is operable or appropriately authorized.&lt;/p&gt;
&lt;p&gt;Verified on &lt;strong&gt;2026-08-01&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="official-sources"&gt;
&lt;a href="#official-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Official sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/home.html"&gt;AWS CDK v2 Developer Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/lambda-runtimes.html"&gt;AWS Lambda runtimes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cdk/v2/guide/permissions.html"&gt;CDK permissions and grants&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html"&gt;API Gateway API types&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Story or Die - How to Use Brain Science to Engage, Persuade, and Change Minds in Business and in Life Summary</title><link>https://www.yopa.page/blog/2023-06-04-story-or-die-summary.html</link><pubDate>Sun, 04 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-06-04-story-or-die-summary.html</guid><description>
&lt;p&gt;In the book &lt;a href="https://amzn.to/3S3WapF"&gt;&amp;ldquo;Story or Die: How to Use Brain Science to Engage, Persuade, and Change Minds in Business and in Life&amp;rdquo;&lt;/a&gt;, the author delves into the fascinating connection between brain science, storytelling, and effective communication. Drawing insights from neuroscience, the book explores why storytelling is a powerful tool to engage, persuade, and ultimately change minds.&lt;/p&gt;
&lt;h2 id="1-the-power-of-storytelling-and-the-brain"&gt;
&lt;a href="#1-the-power-of-storytelling-and-the-brain" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1. The Power of Storytelling and the Brain
&lt;/h2&gt;
&lt;p&gt;Neuroscientist Michael Gazzaniga suggests that our brains primarily evaluate decisions in terms of approaching or withdrawing from a situation. This fundamental aspect of brain functioning underlines the importance of feeling safe when making choices. Consequently, the pursuit of psychological safety becomes crucial in effective communication within organizations.&lt;/p&gt;
&lt;h2 id="2-psychological-safety-and-the-need-for-safety"&gt;
&lt;a href="#2-psychological-safety-and-the-need-for-safety" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2. Psychological Safety and the Need for Safety
&lt;/h2&gt;
&lt;p&gt;Maintaining psychological safety is essential for individuals and organizations. People are wired to avoid threats to their safety, and they prioritize safety over abstract, conceptual, or neutral facts. Even if presented with important information, if it is perceived as a threat to their safety, individuals are likely to resist it.&lt;/p&gt;
&lt;h2 id="3-the-role-of-facts-and-persuasion"&gt;
&lt;a href="#3-the-role-of-facts-and-persuasion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
3. The Role of Facts and Persuasion
&lt;/h2&gt;
&lt;p&gt;Facts alone may not be sufficient for persuasion. To capture attention and achieve persuasion, facts need to clearly demonstrate the outcomes and benefits they bring, not only in terms of safety but also personal growth. &lt;strong&gt;When individuals are convinced that accepting a more correct opinion, despite contradicting their own beliefs, is safer, they become more open to accepting alternative viewpoints&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="4-overcoming-resistance-and-instinctual-knowledge"&gt;
&lt;a href="#4-overcoming-resistance-and-instinctual-knowledge" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
4. Overcoming Resistance and Instinctual Knowledge
&lt;/h2&gt;
&lt;p&gt;Resistance to new ideas or facts stems from an instinctual knowledge that changing the current state can have potentially negative consequences. It is important to understand that people&amp;rsquo;s resistance to change is not due to intellectual deficiencies or flawed personalities but rather an inherent instinct for self-preservation.&lt;/p&gt;
&lt;h2 id="5-the-role-of-leaders-and-organizations"&gt;
&lt;a href="#5-the-role-of-leaders-and-organizations" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
5. The Role of Leaders and Organizations
&lt;/h2&gt;
&lt;p&gt;True leaders and organizations should focus on creating an environment where individuals feel safe to change their thoughts and opinions. Encouraging others to recognize that adopting more accurate perspectives not only ensures their safety but also benefits them personally can foster open-mindedness and growth.&lt;/p&gt;
&lt;h2 id="wrapping-up"&gt;
&lt;a href="#wrapping-up" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Wrapping up!
&lt;/h2&gt;
&lt;p&gt;In conclusion, &lt;a href="https://amzn.to/3S3WapF"&gt;&amp;ldquo;Story or Die: How to Use Brain Science to Engage, Persuade, and Change Minds in Business and in Life&amp;rdquo;&lt;/a&gt; highlights the significance of storytelling and psychological safety in effective communication. By understanding the neuroscience behind decision-making and the need for safety, individuals and organizations can harness the power of storytelling to engage, persuade, and ultimately change minds.&lt;/p&gt;
&lt;p&gt;Cheers! 🍺&lt;/p&gt;</description></item><item><title>Creating an Empty Commit in GitHub</title><link>https://www.yopa.page/blog/2023-06-03-creating-an-empty-commit-in-github.html</link><pubDate>Sat, 03 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-06-03-creating-an-empty-commit-in-github.html</guid><description>
&lt;p&gt;When working with Git and GitHub, commits serve as important milestones that capture meaningful changes in your codebase. However, there may be situations where you need to create an empty commit without any actual code changes. (what if you need to trigger a &lt;strong&gt;Semantic Release&lt;/strong&gt; by commiting.) Although GitHub&amp;rsquo;s user interface doesn&amp;rsquo;t provide a direct option to create an empty commit, you can achieve this through Git commands.&lt;/p&gt;
&lt;h2 id="creating-an-empty-commit"&gt;
&lt;a href="#creating-an-empty-commit" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Creating an Empty Commit
&lt;/h2&gt;
&lt;p&gt;To create an empty commit, use the following Git command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git commit --allow-empty -m &lt;span class="s2"&gt;&amp;#34;Empty commit&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;--allow-empty&lt;/code&gt; flag allows creating a commit without any changes. The &lt;code&gt;-m&lt;/code&gt; option is used to provide a commit message. Replace &lt;code&gt;&amp;quot;Empty commit&amp;quot;&lt;/code&gt; with a meaningful message for your empty commit.&lt;/p&gt;
&lt;h2 id="pushing-the-empty-commit"&gt;
&lt;a href="#pushing-the-empty-commit" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pushing the Empty Commit
&lt;/h2&gt;
&lt;p&gt;To push the empty commit to the remote repository, run the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git push origin &amp;lt;branch-name&amp;gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Replace &lt;code&gt;&amp;lt;branch-name&amp;gt;&lt;/code&gt; with the name of the branch where you want to push the empty commit.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;Even though GitHub&amp;rsquo;s user interface doesn&amp;rsquo;t offer a direct option to create an empty commit, you can achieve this by utilizing Git commands. By following the steps outlined in this article, you can successfully create an empty commit and push it to your remote repository. Empty commits can be useful in certain scenarios, such as triggering build processes or signaling specific events in your version control history. Remember to use them judiciously and ensure that your commit history accurately reflects the changes in your codebase.&lt;/p&gt;</description></item><item><title>Create Salesforce Scratch Org Users with sf CLI</title><link>https://www.yopa.page/blog/2026-08-01-create-salesforce-scratch-org-users-with-sf-cli.html</link><pubDate>Fri, 02 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-create-salesforce-scratch-org-users-with-sf-cli.html</guid><description>
&lt;p&gt;Verified on 2026-08-01. Generic &lt;code&gt;User&lt;/code&gt; DML assembled through shell substitution is fragile and unsafe; required fields, profiles, and licenses vary by org.&lt;/p&gt;
&lt;p&gt;For a scratch org, use the purpose-built command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org create user --target-org scratch &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --set-alias qa-user &lt;span class="nv"&gt;email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;qa@example.invalid &lt;span class="nv"&gt;lastname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Tester
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org assign permset --name App_Tester --target-org qa-user
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Keep user definitions free of real personal data and grant permissions through reviewed permission sets. Scratch-user creation is reproducible and scoped to disposable development orgs. Production user provisioning is a different administrative lifecycle: use your identity/provisioning system or a reviewed API integration with license, profile, MFA, deactivation, and audit controls.&lt;/p&gt;
&lt;p&gt;Migration: delete &lt;code&gt;jq&lt;/code&gt; command substitution, move nonsecret defaults to a user definition, create in a fresh scratch org, verify &lt;code&gt;sf org list users&lt;/code&gt;, then test the intended permission set.&lt;/p&gt;
&lt;p&gt;Reference: &lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-reference/guide/cli_reference_org.html"&gt;Salesforce CLI org commands&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="current-mental-model"&gt;
&lt;a href="#current-mental-model" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Current mental model
&lt;/h2&gt;
&lt;p&gt;A scratch-org user is a disposable test identity derived from licenses and profiles available in that org. It is not a shortcut for production identity lifecycle management. The CLI command understands scratch-user defaults and avoids turning JSON into unquoted shell arguments.&lt;/p&gt;
&lt;h2 id="step-by-step-safe-example"&gt;
&lt;a href="#step-by-step-safe-example" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step-by-step safe example
&lt;/h2&gt;
&lt;p&gt;Confirm the org, inspect existing users, create a narrowly scoped identity, and assign permission sets separately:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org display --target-org scratch
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org list users --target-org scratch
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org create user --target-org scratch --set-alias qa-user &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;email&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;qa@example.invalid &lt;span class="nv"&gt;lastname&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Tester
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org assign permset --name App_Tester --target-org qa-user
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org display user --target-org qa-user
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Use reserved invalid domains for synthetic addresses unless email delivery is part of the test. Never place real employee details or passwords in a committed definition. Permission sets make intent reviewable and avoid cloning an overprivileged profile. If the requested license or profile is unavailable, change the scratch definition or test design rather than borrowing an administrator identity.&lt;/p&gt;
&lt;h2 id="alternatives-and-tradeoffs"&gt;
&lt;a href="#alternatives-and-tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Alternatives and tradeoffs
&lt;/h2&gt;
&lt;p&gt;Inline attributes are quick for one user. A versioned user-definition file is reproducible for a team but must contain only nonsecret synthetic data. Apex or REST creation offers custom logic, yet it exposes org-specific required fields and licensing rules. Production and long-lived sandbox users should come from an identity governance or provisioning process with approval, MFA, deactivation, and auditability.&lt;/p&gt;
&lt;h2 id="migration-and-verification-checklist"&gt;
&lt;a href="#migration-and-verification-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration and verification checklist
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Remove &lt;code&gt;$(jq ...)&lt;/code&gt; and other shell-expanded field lists.&lt;/li&gt;
&lt;li&gt;Separate identity creation from permission assignment.&lt;/li&gt;
&lt;li&gt;Replace personal sample data with synthetic values.&lt;/li&gt;
&lt;li&gt;Recreate the user in a fresh scratch org to test reproducibility.&lt;/li&gt;
&lt;li&gt;Log in as that user and exercise the intended feature, including denied actions.&lt;/li&gt;
&lt;li&gt;Delete the scratch org at the end rather than carrying test identities forward.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Verification is behavioral: listing the user proves creation, while tests under that identity prove the permission boundary.&lt;/p&gt;</description></item><item><title>Tier vs Layer - Understanding the Organization of Software Architecture</title><link>https://www.yopa.page/blog/2023-06-01-tier-vs-layer.html</link><pubDate>Thu, 01 Jun 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-06-01-tier-vs-layer.html</guid><description>
&lt;p&gt;In software architecture, the concepts of &amp;ldquo;tier&amp;rdquo; and &amp;ldquo;layer&amp;rdquo; play a crucial role in organizing and structuring complex systems. While often used interchangeably, understanding the distinctions between these terms is essential for designing scalable and maintainable applications. Let&amp;rsquo;s explore the difference between tiers and layers using a fictional e-commerce platform as an example.&lt;/p&gt;
&lt;h2 id="tiers-dividing-the-application-stack"&gt;
&lt;a href="#tiers-dividing-the-application-stack" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Tiers: Dividing the Application Stack
&lt;/h2&gt;
&lt;p&gt;Tiers refer to logical or physical divisions within an application stack based on functionality or deployment. They represent distinct layers that provide specific services and capabilities. In our e-commerce platform example, we can identify three primary tiers:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Presentation Tier: This tier handles the user interface and interaction. It includes components like web or mobile interfaces, user authentication, and front-end frameworks. Its primary responsibility is to deliver a visually appealing and user-friendly experience.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Application Tier: Also known as the business logic tier, this layer contains the core application logic. It encompasses functionalities such as product catalog management, order processing, and payment integration. The application tier coordinates and orchestrates the various operations required to run the e-commerce platform smoothly.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Data Tier: This tier focuses on data storage and retrieval. It includes databases, data caching mechanisms, and data access layers. In our example, the data tier would store information about products, customer details, and order history. It ensures reliable and efficient data management.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="layers-modular-groupings-within-tiers"&gt;
&lt;a href="#layers-modular-groupings-within-tiers" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Layers: Modular Groupings within Tiers
&lt;/h2&gt;
&lt;p&gt;Layers, on the other hand, are modular groupings or abstractions of components within a specific tier. They provide a structured approach to organizing functionalities and responsibilities. Let&amp;rsquo;s examine the layers within each tier of our e-commerce platform:&lt;/p&gt;
&lt;h3 id="presentation-tier-layers"&gt;
&lt;a href="#presentation-tier-layers" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Presentation Tier Layers
&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;User Interface Layer: This layer encompasses the visual elements and user interaction components. It includes HTML, CSS, JavaScript, and front-end frameworks like React or Angular. Its responsibility is to provide an engaging and intuitive user interface.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Authentication Layer: This layer focuses on user authentication and authorization mechanisms. It ensures secure access control and safeguards user accounts from unauthorized access.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="application-tier-layers"&gt;
&lt;a href="#application-tier-layers" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Application Tier Layers
&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Product Management Layer: This layer handles product-related functionalities such as catalog management, inventory tracking, and pricing. It orchestrates the logic for creating, updating, and displaying product information.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Order Processing Layer: This layer manages the end-to-end order processing flow, including cart management, payment integration, and shipping coordination. It ensures accurate and timely order fulfillment.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 id="data-tier-layers"&gt;
&lt;a href="#data-tier-layers" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Data Tier Layers
&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Database Layer: This layer represents the underlying database management system (DBMS) responsible for data storage and retrieval. It includes database technologies like MySQL, PostgreSQL, or MongoDB.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Data Access Layer: This layer provides an abstraction over the database and handles interactions with the data. It encapsulates queries, data validation, and caching mechanisms, ensuring efficient and secure data access.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;Understanding the distinctions between tiers and layers in software architecture is vital for designing scalable, modular, and maintainable systems. Tiers represent logical or physical divisions based on functionality, while layers provide structured groupings within each tier. By effectively organizing components into tiers and layers, developers can build robust and well-structured applications.&lt;/p&gt;</description></item><item><title>Understanding the Differences Between IaaS, FaaS, and PaaS in Cloud Computing</title><link>https://www.yopa.page/blog/2023-05-31-understanding-the-differences-between-iaas-faas-and-paas-in-cloud-computing.html</link><pubDate>Wed, 31 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-31-understanding-the-differences-between-iaas-faas-and-paas-in-cloud-computing.html</guid><description>
&lt;p&gt;IaaS, PaaS, and FaaS describe different operational boundaries. The useful question is not which acronym is most modern, but which responsibilities a team should own for a particular workload.&lt;/p&gt;
&lt;h2 id="infrastructure-as-a-service-iaas"&gt;
&lt;a href="#infrastructure-as-a-service-iaas" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Infrastructure as a Service (IaaS)
&lt;/h2&gt;
&lt;p&gt;IaaS provides compute, storage, and networking primitives while leaving the guest operating system and application stack to the customer.&lt;/p&gt;
&lt;p&gt;Amazon EC2, Azure Virtual Machines, and Google Compute Engine are familiar examples. This model fits workloads that require operating-system control, specialized networking, custom agents, or software that does not fit a managed platform. That control also brings patching, image management, capacity planning, and host-level observability.&lt;/p&gt;
&lt;h2 id="function-as-a-service-faas"&gt;
&lt;a href="#function-as-a-service-faas" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Function as a Service (FaaS)
&lt;/h2&gt;
&lt;p&gt;FaaS runs event-driven functions without asking the application team to provision servers. AWS Lambda, Azure Functions, and Google Cloud Functions are examples.&lt;/p&gt;
&lt;p&gt;FaaS is one form of serverless computing, not a synonym for every serverless service. It fits short-lived, event-oriented work with variable demand. Teams still own code, dependency security, concurrency behavior, retries, idempotency, observability, and service limits. Long-running processes, specialized runtimes, or predictable sustained loads may fit another compute model better.&lt;/p&gt;
&lt;h2 id="platform-as-a-service-paas"&gt;
&lt;a href="#platform-as-a-service-paas" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Platform as a Service (PaaS)
&lt;/h2&gt;
&lt;p&gt;PaaS accepts an application or container and manages more of the runtime, deployment, routing, health, and scaling layer. Elastic Beanstalk, Azure App Service, and Google App Engine are examples, although current managed-container services also occupy similar territory.&lt;/p&gt;
&lt;p&gt;PaaS is useful when a team wants a conventional long-running application without owning hosts or building an orchestration platform. The tradeoffs are platform constraints, less control over the runtime, service-specific deployment behavior, and possible migration cost.&lt;/p&gt;
&lt;h2 id="choose-by-responsibility"&gt;
&lt;a href="#choose-by-responsibility" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Choose by responsibility
&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Question&lt;/th&gt;
&lt;th&gt;IaaS&lt;/th&gt;
&lt;th&gt;PaaS or managed containers&lt;/th&gt;
&lt;th&gt;FaaS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Need guest OS control?&lt;/td&gt;
&lt;td&gt;Strong fit&lt;/td&gt;
&lt;td&gt;Usually no&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Conventional long-running service?&lt;/td&gt;
&lt;td&gt;Possible, more operations&lt;/td&gt;
&lt;td&gt;Strong fit&lt;/td&gt;
&lt;td&gt;Usually not&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Event-driven, bursty execution?&lt;/td&gt;
&lt;td&gt;Possible, more capacity work&lt;/td&gt;
&lt;td&gt;Possible&lt;/td&gt;
&lt;td&gt;Strong fit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Team owns patching the guest OS?&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Usually no&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Main constraints&lt;/td&gt;
&lt;td&gt;Operations and capacity&lt;/td&gt;
&lt;td&gt;Platform contract&lt;/td&gt;
&lt;td&gt;Runtime, duration, concurrency, event semantics&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Real systems commonly mix these models. A web application might run on a managed container platform, use functions for asynchronous event handling, and retain a small VM for software that requires host access. Decide per component using latency, execution duration, scaling pattern, compliance, portability, team skills, failure recovery, and total operational cost.&lt;/p&gt;
&lt;p&gt;The service-model definitions remain useful, but provider catalogs increasingly form a spectrum rather than three clean boxes. Document the responsibility boundary for the selected service instead of relying on the acronym alone.&lt;/p&gt;
&lt;p&gt;References: &lt;a href="https://csrc.nist.gov/pubs/sp/800/145/final"&gt;NIST SP 800-145, The Definition of Cloud Computing&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/whitepapers/latest/overview-aws-cloud-adoption-framework/platform-perspective.html"&gt;AWS Cloud Adoption Framework: platform perspective&lt;/a&gt;. Verified 2026-08-01.&lt;/p&gt;</description></item><item><title>Guiding Interns in Your Team</title><link>https://www.yopa.page/blog/2023-05-30-guiding-interns-in-your-team.html</link><pubDate>Tue, 30 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-30-guiding-interns-in-your-team.html</guid><description>
&lt;p&gt;As a seasoned professional, it is important to understand the significance of guiding interns effectively within your team. Internships provide valuable learning opportunities, and by offering guidance, you can help shape their professional growth. In this article, we will explore some insightful advice inspired by famous books that can serve as a guiding light in mentoring interns.&lt;/p&gt;
&lt;h2 id="1-"&gt;
&lt;a href="#1-" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
1. &lt;strong&gt;&amp;ldquo;The Alchemist&amp;rdquo; by Paulo Coelho: Unleash their Potential&lt;/strong&gt;
&lt;/h2&gt;
&lt;p&gt;Just as the protagonist Santiago embarks on a transformative journey, your intern is also at the beginning of their career voyage. Approach your role as a mentor with the mindset of an alchemist. Recognize the untapped potential within them and help them discover their unique strengths. Encourage them to experiment with different tasks, departments, and roles, allowing them to unearth their passions and interests.&lt;/p&gt;
&lt;h2 id="2-"&gt;
&lt;a href="#2-" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
2. &lt;strong&gt;&amp;ldquo;The Lean Startup&amp;rdquo; by Eric Ries: Embrace the Iterative Process&lt;/strong&gt;
&lt;/h2&gt;
&lt;p&gt;Eric Ries emphasizes the importance of an iterative approach in startups, and this principle can be applied to guiding interns as well. Encourage them to adopt a mindset of continuous learning and improvement. Provide regular feedback and set short-term goals, enabling them to make incremental progress. Emphasize that failures are stepping stones to success, and that learning from mistakes is an integral part of professional growth.&lt;/p&gt;
&lt;h2 id="3-"&gt;
&lt;a href="#3-" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
3. &lt;strong&gt;&amp;ldquo;Quiet: The Power of Introverts in a World That Can&amp;rsquo;t Stop Talking&amp;rdquo; by Susan Cain: Nurture Diverse Talents&lt;/strong&gt;
&lt;/h2&gt;
&lt;p&gt;Interns come from diverse backgrounds, and it is essential to recognize and appreciate the unique talents they bring to the team. Drawing inspiration from Susan Cain&amp;rsquo;s book, understand that introverted individuals may possess exceptional analytical skills and a propensity for introspection. Create an inclusive environment that caters to different personalities, allowing interns to thrive and contribute in their own unique way.&lt;/p&gt;
&lt;h2 id="4-"&gt;
&lt;a href="#4-" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
4. &lt;strong&gt;&amp;ldquo;The 7 Habits of Highly Effective People&amp;rdquo; by Stephen R. Covey: Foster Independence and Proactivity&lt;/strong&gt;
&lt;/h2&gt;
&lt;p&gt;In Stephen R. Covey&amp;rsquo;s timeless book, he shares seven habits that lead to success. Two key habits, &amp;ldquo;Be Proactive&amp;rdquo; and &amp;ldquo;Begin with the End in Mind,&amp;rdquo; are particularly relevant when guiding interns. Encourage them to take ownership of their work and projects. Set clear objectives and allow them to devise their own plans to achieve those goals. This approach fosters independence, responsibility, and a sense of accountability.&lt;/p&gt;
&lt;h2 id="5-"&gt;
&lt;a href="#5-" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
5. &lt;strong&gt;&amp;ldquo;The Art of War&amp;rdquo; by Sun Tzu: Cultivate a Supportive Environment&lt;/strong&gt;
&lt;/h2&gt;
&lt;p&gt;Sun Tzu&amp;rsquo;s ancient treatise on warfare provides valuable insights on leadership and strategy. While the battlefield might seem far removed from the office, the concept of fostering a supportive environment applies universally. Be a mentor who leads by example, offering guidance and support while allowing interns to learn from their experiences. Create an atmosphere that encourages open communication, collaboration, and camaraderie among team members.&lt;/p&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;Guiding interns effectively requires patience, empathy, and an understanding of their unique needs and aspirations. By incorporating lessons from famous books into your mentoring approach, you can inspire and empower interns to reach their full potential. Remember, as a mentor, your guidance can have a profound impact on shaping their future careers. Embrace this opportunity to make a positive difference and create a legacy that extends far beyond their time as interns.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Note: The book references mentioned in this article serve as metaphors and sources of inspiration for guiding interns, and their concepts should be adapted and applied in a manner appropriate to the professional context.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Cheers! 🍺&lt;/p&gt;</description></item><item><title>The Pragmatic Programmer; Bend or Break</title><link>https://www.yopa.page/blog/2023-05-29-the-pragmatic-programmer-bend-or-break.html</link><pubDate>Mon, 29 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-29-the-pragmatic-programmer-bend-or-break.html</guid><description>
&lt;h2 id="bend-or-break-embracing-adaptability"&gt;
&lt;a href="#bend-or-break-embracing-adaptability" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Bend or Break: Embracing Adaptability
&lt;/h2&gt;
&lt;p&gt;The &lt;strong&gt;Bend-or-Break&lt;/strong&gt; principle encourages software developers to be flexible and adaptable in the face of changing requirements, technologies, and project circumstances. Instead of clinging rigidly to predefined plans or solutions, this philosophy emphasizes the need to adjust, iterate, and overcome challenges by bending existing strategies or breaking away from them altogether.&lt;/p&gt;
&lt;h3 id="real-life-examples"&gt;
&lt;a href="#real-life-examples" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Real-Life Examples
&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Mobile Application Development&lt;/strong&gt;: Consider a team developing a mobile application for a startup. During the initial planning phase, they decide to build the app for a specific platform. However, as the project progresses, they realize that the target market prefers a different platform. To adapt, the team must be willing to break away from their original plan and pivot towards the new platform, rather than persisting with a less suitable solution.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Agile Development&lt;/strong&gt;: Agile methodologies, such as Scrum, embody the bend-or-break philosophy. Agile teams embrace change and regularly adapt their plans based on customer feedback and evolving requirements. They recognize that being rigid and unwilling to adjust can hinder progress and limit the potential for delivering valuable software.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Software Maintenance&lt;/strong&gt;: When maintaining legacy systems, developers often encounter outdated technologies or code that no longer aligns with modern best practices. To ensure the continued functionality and improvement of the system, developers must be willing to bend the existing codebase, refactoring and modernizing it over time, or even breaking away from it completely and rebuilding certain components to ensure scalability and maintainability.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="the-benefits-of-adaptability"&gt;
&lt;a href="#the-benefits-of-adaptability" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Benefits of Adaptability
&lt;/h2&gt;
&lt;p&gt;Embracing the bend-or-break mindset offers several advantages to software developers and teams:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Resilience&lt;/strong&gt;: Adaptability enables programmers to navigate unexpected challenges and respond effectively to shifting circumstances. By bending or breaking, developers can overcome obstacles, meet evolving requirements, and deliver valuable solutions.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Innovation&lt;/strong&gt;: Flexibility allows developers to explore new technologies, frameworks, and methodologies. By being open to change, they can experiment, learn, and leverage emerging trends to create innovative solutions that better meet user needs.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Continuous Learning&lt;/strong&gt;: Adapting to change necessitates acquiring new knowledge and skills. The bend-or-break philosophy promotes a culture of continuous learning, empowering developers to expand their expertise and stay up-to-date with advancements in the field.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Delivering Value&lt;/strong&gt;: By embracing adaptability, developers can deliver software that better aligns with user expectations and market demands. Being responsive to change increases the likelihood of delivering timely, relevant, and high-quality products.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="wrapping-up"&gt;
&lt;a href="#wrapping-up" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Wrapping up
&lt;/h2&gt;
&lt;p&gt;In the fast-paced world of software development, the bend-or-break philosophy outlined in &amp;ldquo;The Pragmatic Programmer&amp;rdquo; is crucial. Embracing adaptability allows programmers to thrive amidst changing requirements, emerging technologies, and evolving project circumstances. By being willing to bend existing plans and strategies or break away from them when necessary, developers can foster resilience, drive innovation, embrace continuous learning, and ultimately deliver valuable software solutions.&lt;/p&gt;
&lt;p&gt;Cheers! 🍺&lt;/p&gt;</description></item><item><title>The Pragmatic Programmer; A Pragmatic Approach</title><link>https://www.yopa.page/blog/2023-05-28-the-pragmatic-programmer-a-pragmatic-approach.html</link><pubDate>Sun, 28 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-28-the-pragmatic-programmer-a-pragmatic-approach.html</guid><description>
&lt;p&gt;In &lt;em&gt;The Pragmatic Programmer&lt;/em&gt;, Andrew Hunt and David Thomas present a wealth of wisdom and practical guidance for software developers. Section 3 of the book, titled &amp;ldquo;A Pragmatic Approach,&amp;rdquo; dives into the essence of pragmatic programming and how it can revolutionize the way we approach software development.&lt;/p&gt;
&lt;h2 id="embracing-pragmatic-programming"&gt;
&lt;a href="#embracing-pragmatic-programming" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Embracing Pragmatic Programming
&lt;/h2&gt;
&lt;p&gt;A pragmatic approach to software development revolves around practicality, effectiveness, and adaptability. It encourages programmers to go beyond following strict methodologies and instead focus on finding practical solutions that deliver value to users. Let&amp;rsquo;s explore the key principles and practices that define a pragmatic approach.&lt;/p&gt;
&lt;h3 id="deadlines-as-opportunities"&gt;
&lt;a href="#deadlines-as-opportunities" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Deadlines as Opportunities
&lt;/h3&gt;
&lt;p&gt;Pragmatic programmers view deadlines not as constraints, but as opportunities for prioritization and delivering incremental value. By embracing realistic deadlines, developers can focus on delivering the most critical features first and iterating on their work over time. This approach allows for faster feedback cycles and aligns development efforts with user needs.&lt;/p&gt;
&lt;h3 id="maintainable-code-the-golden-rule"&gt;
&lt;a href="#maintainable-code-the-golden-rule" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Maintainable Code: The Golden Rule
&lt;/h3&gt;
&lt;p&gt;Writing maintainable code lies at the core of a pragmatic approach. Pragmatic programmers recognize that code is read more often than it is written. They prioritize writing clear, well-structured code that is easy to understand and modify. By considering future maintainers of the codebase, they ensure long-term productivity and reduce technical debt.&lt;/p&gt;
&lt;h3 id="debugging-thinking-outside-the-box"&gt;
&lt;a href="#debugging-thinking-outside-the-box" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Debugging: Thinking Outside the Box
&lt;/h3&gt;
&lt;p&gt;When facing software bugs, a pragmatic programmer employs a systematic and creative approach to debugging. They go beyond simply fixing the immediate symptom and strive to understand the underlying cause. Techniques like rubber duck debugging, where programmers explain their code to an inanimate object, help uncover hidden issues and encourage problem-solving from different perspectives.&lt;/p&gt;
&lt;h3 id="automation-working-smarter-not-harder"&gt;
&lt;a href="#automation-working-smarter-not-harder" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Automation: Working Smarter, Not Harder
&lt;/h3&gt;
&lt;p&gt;Automation is a cornerstone of pragmatic programming. By automating repetitive tasks and building efficient workflows, developers can save time and reduce errors. Whether it&amp;rsquo;s automating code formatting, setting up continuous integration pipelines, or creating scripts for deployment, automation empowers programmers to work smarter and focus on higher-value activities.&lt;/p&gt;
&lt;h3 id="pragmatic-testing-balancing-coverage-and-efficiency"&gt;
&lt;a href="#pragmatic-testing-balancing-coverage-and-efficiency" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pragmatic Testing: Balancing Coverage and Efficiency
&lt;/h3&gt;
&lt;p&gt;Testing is a vital component of pragmatic programming. Pragmatic programmers aim for a balanced approach to testing, seeking an optimal trade-off between coverage and efficiency. They prioritize critical tests and focus on generating meaningful test cases that cover a wide range of scenarios, while avoiding excessive redundancy that could hamper development speed.&lt;/p&gt;
&lt;h2 id="revolutionizing-software-development-with-a-pragmatic-approach"&gt;
&lt;a href="#revolutionizing-software-development-with-a-pragmatic-approach" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Revolutionizing Software Development with a Pragmatic Approach
&lt;/h2&gt;
&lt;p&gt;A pragmatic approach to software development enables programmers to adapt, evolve, and thrive in the ever-changing landscape of technology. By embracing practicality, maintaining code quality, employing effective debugging techniques, leveraging automation, and adopting a balanced testing strategy, developers can deliver high-quality software that meets user needs while maximizing efficiency.&lt;/p&gt;
&lt;p&gt;Let us embark on this journey of pragmatic programming, where innovation and practicality go hand in hand, and software development becomes a delightful and productive endeavor.&lt;/p&gt;
&lt;p&gt;Cheers! 🍺&lt;/p&gt;</description></item><item><title>The Pragmatic Programmer; A Pragmatic Philosophy</title><link>https://www.yopa.page/blog/2023-05-27-the-pragmatic-programmer-a-pragmatic-philosophy.html</link><pubDate>Sat, 27 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-27-the-pragmatic-programmer-a-pragmatic-philosophy.html</guid><description>
&lt;p&gt;&lt;em&gt;The Pragmatic Programmer&lt;/em&gt; is a renowned book written by Andrew Hunt and David Thomas, offering valuable insights and practical advice for software developers. In Section 2 of the book, titled &amp;ldquo;A Pragmatic Philosophy,&amp;rdquo; the authors introduce a mindset and set of principles that form the foundation of a pragmatic approach to programming. Let&amp;rsquo;s explore this philosophy and its real-life applications.&lt;/p&gt;
&lt;h2 id="the-cat-ate-my-source-code"&gt;
&lt;a href="#the-cat-ate-my-source-code" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Cat Ate My Source Code
&lt;/h2&gt;
&lt;p&gt;One key aspect of the pragmatic philosophy is taking responsibility for our work. It&amp;rsquo;s easy to blame external factors or circumstances when things go wrong, but a pragmatic programmer acknowledges their role in the development process. For example, imagine a software developer who encounters a bug in their code. Instead of blaming the lack of clear requirements or time constraints, they focus on understanding and rectifying the issue, taking ownership of their codebase.&lt;/p&gt;
&lt;h2 id="software-entropy-the-constant-battle"&gt;
&lt;a href="#software-entropy-the-constant-battle" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Software Entropy: The Constant Battle
&lt;/h2&gt;
&lt;p&gt;Software entropy refers to the tendency of software systems to become more complex and disorganized over time. A pragmatic programmer recognizes the need to combat entropy through regular code maintenance, refactoring, and clean-up efforts. Consider a scenario where a development team notices their codebase becoming difficult to maintain due to increasing complexity. They adopt a pragmatic approach by scheduling regular refactoring sessions and implementing automated tests to catch regressions, thereby managing and reducing software entropy.&lt;/p&gt;
&lt;h2 id="stone-soup-and-boiled-frogs-the-power-of-incremental-change"&gt;
&lt;a href="#stone-soup-and-boiled-frogs-the-power-of-incremental-change" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Stone Soup and Boiled Frogs: The Power of Incremental Change
&lt;/h2&gt;
&lt;p&gt;The metaphors of stone soup and boiled frogs illustrate the value of incremental change. In software development, making small, meaningful improvements over time can lead to substantial progress. For instance, imagine a project manager who wants to introduce a new development process to improve team efficiency. Instead of imposing a drastic change overnight, they take a pragmatic approach by gradually introducing small process tweaks, encouraging feedback from the team, and iteratively refining the process based on real-world outcomes.&lt;/p&gt;
&lt;h2 id="good-enough-software-balancing-perfectionism-and-pragmatism"&gt;
&lt;a href="#good-enough-software-balancing-perfectionism-and-pragmatism" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Good Enough Software: Balancing Perfectionism and Pragmatism
&lt;/h2&gt;
&lt;p&gt;The pursuit of perfection can hinder progress in software development. The pragmatic philosophy suggests focusing on delivering &amp;ldquo;good enough&amp;rdquo; software that meets current requirements while maintaining a high level of quality. For instance, imagine a startup developing a minimum viable product (MVP) to test its viability in the market. Instead of spending excessive time on refining every feature, they adopt a pragmatic approach by prioritizing core functionality and delivering a working product within the available resources and timeframe.&lt;/p&gt;
&lt;h2 id="your-knowledge-portfolio-investing-in-continuous-learning"&gt;
&lt;a href="#your-knowledge-portfolio-investing-in-continuous-learning" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Your Knowledge Portfolio: Investing in Continuous Learning
&lt;/h2&gt;
&lt;p&gt;A pragmatic programmer understands the importance of continually expanding their knowledge and skills. They treat their professional development as an investment portfolio, diversifying their expertise and regularly updating existing skills. For example, a software developer may invest time in learning a new programming language or exploring emerging technologies that align with their career goals. By maintaining an up-to-date knowledge portfolio, they remain adaptable and valuable in a rapidly evolving industry.&lt;/p&gt;
&lt;p&gt;We will read the Section 3, A &lt;strong&gt;Pragmatic Approach&lt;/strong&gt;, tomorrow.&lt;/p&gt;
&lt;p&gt;Cheers! 🍺&lt;/p&gt;</description></item><item><title>A Summary of Surrounded by Idiots by Thomas Erikson</title><link>https://www.yopa.page/blog/2023-05-26-a-summary-of-surrounded-by-idiots-by-thomas-erikson.html</link><pubDate>Fri, 26 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-26-a-summary-of-surrounded-by-idiots-by-thomas-erikson.html</guid><description>
&lt;p&gt;Alright, listen up aspiring code nerds! (in a good way ;) If you want to rock the software engineering realm, there&amp;rsquo;s a crucial superpower you gotta wield: communication skills! Even if you couldn&amp;rsquo;t care less about the crazy lives of your fellow humanoids, you simply can&amp;rsquo;t dodge the inevitable communication or chit-chat. So, brace yourselves, folks! Embrace the unavoidable art of conversation like a true champ, or prepare to be stuck in an eternal bug-fixing abyss.&lt;/p&gt;
&lt;h2 id="understanding-and-applying-the-four-main-communication-styles-red-yellow-green-and-blue"&gt;
&lt;a href="#understanding-and-applying-the-four-main-communication-styles-red-yellow-green-and-blue" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Understanding and Applying the Four Main Communication Styles: Red, Yellow, Green, and Blue
&lt;/h2&gt;
&lt;p&gt;In the international bestseller &amp;ldquo;Surrounded by Idiots,&amp;rdquo; Thomas Erikson provides a unique perspective on understanding and dealing with different communication styles. Grounded in the DISC behavioral assessment tool, Erikson assigns colors to these styles: Red, Yellow, Green, and Blue. This system allows for a more effective approach to interpersonal communication.&lt;/p&gt;
&lt;h2 id="1-red-dominance"&gt;
&lt;a href="#1-red-dominance" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;1. Red (Dominance)&lt;/strong&gt;
&lt;/h2&gt;
&lt;p&gt;Red individuals are characterized by their determination, strong will, and ability to maintain control. They are dynamic, active, and task-oriented.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key traits:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Decisive:&lt;/strong&gt; They make quick decisions and are driven by power and control.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Goal-oriented:&lt;/strong&gt; Reds thrive on challenges and are often competitive.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Impatient:&lt;/strong&gt; They prefer quick actions and results.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Communication style:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Reds prefer direct, clear, and to-the-point communication. They value efficiency and may become impatient with lengthy discussions.&lt;/p&gt;
&lt;p&gt;When communicating with Red individuals, clarity and brevity are key. State your points in a structured, concise manner, focusing on the results or outcomes. Avoid giving too many details or lengthy explanations.&lt;/p&gt;
&lt;h2 id="2-yellow-influence"&gt;
&lt;a href="#2-yellow-influence" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;2. Yellow (Influence)&lt;/strong&gt;
&lt;/h2&gt;
&lt;p&gt;Yellow individuals are sociable, expressive, and dynamic. They thrive in environments where interaction and socializing are encouraged.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key traits:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Enthusiastic:&lt;/strong&gt; Yellows are often the life of the party, bringing energy and positivity wherever they go.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Persuasive:&lt;/strong&gt; They excel at influencing and inspiring others.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Optimistic:&lt;/strong&gt; Yellows often see the brighter side of things, maintaining a positive outlook.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Communication style:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Yellows enjoy interactive and engaging communication. They enjoy storytelling and use humor frequently.&lt;/p&gt;
&lt;p&gt;When communicating with Yellow individuals, adopt an engaging, positive, and friendly approach. Avoid being overly formal or negative. Yellows prefer exciting ideas, stories, and an energetic tone.&lt;/p&gt;
&lt;h2 id="3-green-steadiness"&gt;
&lt;a href="#3-green-steadiness" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;3. Green (Steadiness)&lt;/strong&gt;
&lt;/h2&gt;
&lt;p&gt;Green individuals prioritize relationships and are patient, supportive, and relaxed. They value cooperation and prefer a peaceful environment.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key traits:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Reliable:&lt;/strong&gt; Greens are dependable and consistent.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Patient:&lt;/strong&gt; They are tolerant and tend to avoid conflicts.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Team-oriented:&lt;/strong&gt; Greens work well in a team setting, prioritizing group harmony.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Communication style:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Greens communicate in a calm, patient manner, preferring to listen first and speak second. They value relationships and focus on understanding others&amp;rsquo; viewpoints.&lt;/p&gt;
&lt;p&gt;When communicating with Green individuals, demonstrate patience, understanding, and empathy. Show genuine interest in their thoughts and feelings. Avoid aggression, confrontation, or a rushed approach.&lt;/p&gt;
&lt;h2 id="4-blue-conscientiousness"&gt;
&lt;a href="#4-blue-conscientiousness" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
&lt;strong&gt;4. Blue (Conscientiousness)&lt;/strong&gt;
&lt;/h2&gt;
&lt;p&gt;Blue individuals are analytical, meticulous, and systematic. They value accuracy and logic.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key traits:&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Analytical:&lt;/strong&gt; Blues prefer to thoroughly examine information before making decisions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Detail-oriented:&lt;/strong&gt; They pay careful attention to details and value precision.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Systematic:&lt;/strong&gt; Blues follow specific processes or systems.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;Communication style:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Blues communicate in a logical, systematic manner. They prefer facts, details, and well-structured arguments.&lt;/p&gt;
&lt;p&gt;When communicating with Blue individuals, be analytical, logical, and accurate. They appreciate facts and precise details. Be prepared to answer questions and provide evidence for your statements. Avoid emotional arguments, ambiguity, or inconsistency.&lt;/p&gt;
&lt;h2 id="wrapping-up"&gt;
&lt;a href="#wrapping-up" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Wrapping up.
&lt;/h2&gt;
&lt;p&gt;Understanding these four communication styles can greatly improve your interpersonal relationships and effectiveness at work. However, keep in mind that these are guidelines, and real people may exhibit traits from several colors. The objective is to make your interlocutor feel understood, respected, and comfortable during the conversation by adapting your communication style to theirs.&lt;/p&gt;
&lt;p&gt;Now, if you can categorize your co-workers into these convenient color compartments, congratulations, you&amp;rsquo;ve passed Communication 101. But don&amp;rsquo;t pop the champagne yet, that&amp;rsquo;s just the beginning. Keep stuggling until you master it.&lt;/p&gt;
&lt;p&gt;Cheers! 🍺&lt;/p&gt;</description></item><item><title>The Rules of Work by Richard Templar</title><link>https://www.yopa.page/blog/2023-05-25-the-rules-of-work-by-richard-templar.html</link><pubDate>Thu, 25 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-25-the-rules-of-work-by-richard-templar.html</guid><description>
&lt;p&gt;&lt;strong&gt;The Rules of Work&lt;/strong&gt; is a practical guide by Richard Templar. It is composed of 108 rules that guide professionals in navigating their work lives more efficiently and effectively. In this summary, we will highlight some of the key points that Templar emphasizes in his influential work.&lt;/p&gt;
&lt;h2 id="introduction"&gt;
&lt;a href="#introduction" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Introduction
&lt;/h2&gt;
&lt;p&gt;Templar&amp;rsquo;s book offers a fresh perspective on the unspoken rules that dictate success in the workplace. The author&amp;rsquo;s rules are intended to make one more professional, confident, and ultimately, more successful at work. The book is divided into ten parts, each highlighting different facets of the workplace.&lt;/p&gt;
&lt;h2 id="walk-your-talk"&gt;
&lt;a href="#walk-your-talk" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Walk Your Talk
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Get your work noticed.&lt;/li&gt;
&lt;li&gt;Never stand still.&lt;/li&gt;
&lt;li&gt;Volunteer carefully.&lt;/li&gt;
&lt;li&gt;Carve out a niche for yourself.&lt;/li&gt;
&lt;li&gt;Under promise and over deliver.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="know-that-youre-being-judged-at-all-times"&gt;
&lt;a href="#know-that-youre-being-judged-at-all-times" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Know That You&amp;rsquo;re Being Judged at All Times
&lt;/h2&gt;
&lt;ol start="6"&gt;
&lt;li&gt;Cultivate a smile.&lt;/li&gt;
&lt;li&gt;Dress well.&lt;/li&gt;
&lt;li&gt;Cultivate the art of speech.&lt;/li&gt;
&lt;li&gt;Be aware of personal space.&lt;/li&gt;
&lt;li&gt;Practice being liked.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="have-a-plan"&gt;
&lt;a href="#have-a-plan" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Have a Plan
&lt;/h2&gt;
&lt;ol start="11"&gt;
&lt;li&gt;Know what you want long term.&lt;/li&gt;
&lt;li&gt;Know what you want short term.&lt;/li&gt;
&lt;li&gt;Study the promotion system.&lt;/li&gt;
&lt;li&gt;Develop a game plan.&lt;/li&gt;
&lt;li&gt;Set objectives.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="if-you-cant-say-anything-nice-shut-up"&gt;
&lt;a href="#if-you-cant-say-anything-nice-shut-up" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
If You Can&amp;rsquo;t Say Anything Nice, Shut Up
&lt;/h2&gt;
&lt;ol start="16"&gt;
&lt;li&gt;Don&amp;rsquo;t gossip.&lt;/li&gt;
&lt;li&gt;Be cheerful and positive.&lt;/li&gt;
&lt;li&gt;Keep your personal life to yourself.&lt;/li&gt;
&lt;li&gt;Be a good listener.&lt;/li&gt;
&lt;li&gt;Only speak sense.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="look-after-yourself"&gt;
&lt;a href="#look-after-yourself" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Look After Yourself
&lt;/h2&gt;
&lt;ol start="21"&gt;
&lt;li&gt;Know the ethics of your industry.&lt;/li&gt;
&lt;li&gt;Know the legalities of your industry.&lt;/li&gt;
&lt;li&gt;Set personal standards.&lt;/li&gt;
&lt;li&gt;Have positive personal ambitions.&lt;/li&gt;
&lt;li&gt;Be physically fit.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="blend-in"&gt;
&lt;a href="#blend-in" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Blend In
&lt;/h2&gt;
&lt;ol start="26"&gt;
&lt;li&gt;Know the corporate culture.&lt;/li&gt;
&lt;li&gt;Speak the language.&lt;/li&gt;
&lt;li&gt;Dress up or down accordingly.&lt;/li&gt;
&lt;li&gt;Be adaptable in your dealings with different people.&lt;/li&gt;
&lt;li&gt;Know where to hang out, and when.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="act-one-step-ahead"&gt;
&lt;a href="#act-one-step-ahead" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Act One Step Ahead
&lt;/h2&gt;
&lt;ol start="31"&gt;
&lt;li&gt;Dress one step ahead.&lt;/li&gt;
&lt;li&gt;Talk one step ahead.&lt;/li&gt;
&lt;li&gt;Act one step ahead.&lt;/li&gt;
&lt;li&gt;Think one step ahead.&lt;/li&gt;
&lt;li&gt;Address corporate issues and problems one step ahead.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="cultivate-diplomacy"&gt;
&lt;a href="#cultivate-diplomacy" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Cultivate Diplomacy
&lt;/h2&gt;
&lt;ol start="36"&gt;
&lt;li&gt;Know the system — and milk it.&lt;/li&gt;
&lt;li&gt;Be wise with information.&lt;/li&gt;
&lt;li&gt;Beware of the office politics.&lt;/li&gt;
&lt;li&gt;Be discreet.&lt;/li&gt;
&lt;li&gt;Cultivate the friendship and respect of your colleagues.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="know-the-rules-about-authority"&gt;
&lt;a href="#know-the-rules-about-authority" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Know the Rules About Authority
&lt;/h2&gt;
&lt;ol start="41"&gt;
&lt;li&gt;Understand the hierarchy.&lt;/li&gt;
&lt;li&gt;Know who the real bosses are.&lt;/li&gt;
&lt;li&gt;Know the people who know the people.&lt;/li&gt;
&lt;li&gt;Never disapprove of others.&lt;/li&gt;
&lt;li&gt;Understand the role of the job.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="play-the-game"&gt;
&lt;a href="#play-the-game" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Play the Game
&lt;/h2&gt;
&lt;ol start="46"&gt;
&lt;li&gt;Know that we&amp;rsquo;re all playing a game.&lt;/li&gt;
&lt;li&gt;Be enthusiastic.&lt;/li&gt;
&lt;li&gt;Be a &amp;lsquo;can-do&amp;rsquo; person.&lt;/li&gt;
&lt;li&gt;Be 100% committed.&lt;/li&gt;
&lt;li&gt;Maintain a sense of humor.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="look-out-for-the-future"&gt;
&lt;a href="#look-out-for-the-future" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Look Out for the Future
&lt;/h2&gt;
&lt;ol start="51"&gt;
&lt;li&gt;Know the business you&amp;rsquo;re in.&lt;/li&gt;
&lt;li&gt;Constantly reassess your position.&lt;/li&gt;
&lt;li&gt;Know where you want to go.&lt;/li&gt;
&lt;li&gt;Make sure your manager knows you want to progress.&lt;/li&gt;
&lt;li&gt;Study the industry.&lt;/li&gt;
&lt;li&gt;Study the competition.&lt;/li&gt;
&lt;li&gt;Study your organization.&lt;/li&gt;
&lt;li&gt;Study your department.&lt;/li&gt;
&lt;li&gt;Study your job.&lt;/li&gt;
&lt;li&gt;Know the skills of the future and acquire them.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="know-when-to-break-the-rules"&gt;
&lt;a href="#know-when-to-break-the-rules" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Know When to Break the Rules
&lt;/h2&gt;
&lt;ol start="61"&gt;
&lt;li&gt;Know which rules to break.&lt;/li&gt;
&lt;li&gt;Be subtle.&lt;/li&gt;
&lt;li&gt;Bend the rules, don&amp;rsquo;t break them.&lt;/li&gt;
&lt;li&gt;Make sure breaking the rule benefits the company.&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t get caught.&lt;/li&gt;
&lt;li&gt;Do it with humor.&lt;/li&gt;
&lt;li&gt;Know the exceptions to the rule.&lt;/li&gt;
&lt;li&gt;Know that there&amp;rsquo;s always an exception to the rule.&lt;/li&gt;
&lt;li&gt;Be paradoxical.&lt;/li&gt;
&lt;li&gt;If in doubt, don&amp;rsquo;t.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="be-good-at-pr"&gt;
&lt;a href="#be-good-at-pr" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Be Good at PR
&lt;/h2&gt;
&lt;ol start="71"&gt;
&lt;li&gt;Know the benefits of networking.&lt;/li&gt;
&lt;li&gt;Create a network.&lt;/li&gt;
&lt;li&gt;Maintain your network.&lt;/li&gt;
&lt;li&gt;Use your network.&lt;/li&gt;
&lt;li&gt;Enjoy your network.&lt;/li&gt;
&lt;li&gt;Be known for one thing.&lt;/li&gt;
&lt;li&gt;Self-publicize.&lt;/li&gt;
&lt;li&gt;Project a consistent image.&lt;/li&gt;
&lt;li&gt;Make sure your manager knows how good you are.&lt;/li&gt;
&lt;li&gt;Be modest.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="know-the-system"&gt;
&lt;a href="#know-the-system" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Know the System
&lt;/h2&gt;
&lt;ol start="81"&gt;
&lt;li&gt;Understand the company&amp;rsquo;s philosophy.&lt;/li&gt;
&lt;li&gt;Learn the unspoken rules.&lt;/li&gt;
&lt;li&gt;Know the rules about the money.&lt;/li&gt;
&lt;li&gt;Know the rules about communicating.&lt;/li&gt;
&lt;li&gt;Know the rules about sex.&lt;/li&gt;
&lt;li&gt;Know the rules about company property.&lt;/li&gt;
&lt;li&gt;Know what gets rewarded.&lt;/li&gt;
&lt;li&gt;Know what doesn&amp;rsquo;t get rewarded.&lt;/li&gt;
&lt;li&gt;Know when to stand by the rules.&lt;/li&gt;
&lt;li&gt;Know when to ignore them.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="dont-push-too-hard"&gt;
&lt;a href="#dont-push-too-hard" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Don’t Push Too Hard
&lt;/h2&gt;
&lt;ol start="91"&gt;
&lt;li&gt;Don&amp;rsquo;t badmouth the competition.&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t badmouth the company.&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t whine.&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t rely on one ally.&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t hide behind your job description.&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t have favorites.&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t be irreplaceable.&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t carry tales.&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t rely on status.&lt;/li&gt;
&lt;li&gt;Don&amp;rsquo;t push your luck.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="know-the-tricks"&gt;
&lt;a href="#know-the-tricks" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Know the Tricks
&lt;/h2&gt;
&lt;ol start="101"&gt;
&lt;li&gt;Watch for game players.&lt;/li&gt;
&lt;li&gt;Identify the company politics.&lt;/li&gt;
&lt;li&gt;Be aware of personal agendas.&lt;/li&gt;
&lt;li&gt;Beware of the insecure.&lt;/li&gt;
&lt;li&gt;Watch out for the ambitious.&lt;/li&gt;
&lt;li&gt;Understand power structures.&lt;/li&gt;
&lt;li&gt;Recognize power plays.&lt;/li&gt;
&lt;li&gt;Know when to leave.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="conclusion"&gt;
&lt;a href="#conclusion" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Conclusion
&lt;/h2&gt;
&lt;p&gt;The Rules of Work presents a pragmatic and effective roadmap for professional success. It provides guidance on how to behave, how to look, and how to navigate workplace dynamics effectively. Templar&amp;rsquo;s rules are not about manipulating others, but about helping professionals become adept at working within the corporate culture to their advantage.&lt;/p&gt;
&lt;p&gt;In essence, Templar&amp;rsquo;s advice is centered around becoming a good team player, maintaining professionalism, and balancing work with a healthy lifestyle. It&amp;rsquo;s a must-read for anyone serious about making a mark in their professional life.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;“It’s all a game, and the Rules of Work are the rules of the game. We all have to go to work, and we all play the game. The winners will be the ones who are very good at playing the game professionally,” - Richard Templar.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Cheers! 🍺&lt;/p&gt;</description></item><item><title>Walking the Directory and Creating a TreeNode</title><link>https://www.yopa.page/blog/2023-05-24-walking-the-directory-and-creating-treenode.html</link><pubDate>Wed, 24 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-24-walking-the-directory-and-creating-treenode.html</guid><description>
&lt;h2 id="a-non-blocking-traversal-for-new-code"&gt;
&lt;a href="#a-non-blocking-traversal-for-new-code" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A non-blocking traversal for new code
&lt;/h2&gt;
&lt;p&gt;For new code, prefer &lt;code&gt;fs.promises.readdir(path, { withFileTypes: true })&lt;/code&gt; so traversal does not block the event loop or call &lt;code&gt;stat&lt;/code&gt; for every entry. Decide explicitly how to handle symbolic links, permission errors, cycles, and paths that disappear during traversal.&lt;/p&gt;
&lt;h1 id="walking-the-directory-and-creating-a-treenode-in-nodejs"&gt;
&lt;a href="#walking-the-directory-and-creating-a-treenode-in-nodejs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Walking the Directory and Creating a TreeNode in Node.js
&lt;/h1&gt;
&lt;p&gt;In this article, we will be exploring how to navigate or walk through a file directory in Node.js, creating a &lt;code&gt;TreeNode&lt;/code&gt; for each file or subdirectory we encounter. This will involve using Node&amp;rsquo;s built-in &lt;code&gt;fs&lt;/code&gt; (file system) and &lt;code&gt;path&lt;/code&gt; modules, as well as the &lt;code&gt;crypto&lt;/code&gt; module for hashing file contents.&lt;/p&gt;
&lt;p&gt;First, let&amp;rsquo;s begin by creating an &lt;code&gt;interface&lt;/code&gt; for our file metadata:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;FileMetadata&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;path&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;isDirectory&lt;/span&gt;: &lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;hash?&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Here, our &lt;code&gt;FileMetadata&lt;/code&gt; interface defines three properties: &lt;code&gt;path&lt;/code&gt; (the file or directory path), &lt;code&gt;isDirectory&lt;/code&gt; (a boolean indicating if the path is a directory), and &lt;code&gt;hash&lt;/code&gt; (a string representing a file hash, which will be optional since directories won&amp;rsquo;t have a hash).&lt;/p&gt;
&lt;p&gt;Next, we create a &lt;code&gt;TreeNode&lt;/code&gt; class:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;TreeNode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;: &lt;span class="kt"&gt;FileMetadata&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;children&lt;/span&gt;: &lt;span class="kt"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;: &lt;span class="kt"&gt;FileMetadata&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;: &lt;span class="kt"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isDirectory&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;Dir: &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;File: &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;metadata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;child&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;child&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;level&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;TreeNode&lt;/code&gt; class represents a node in our tree structure. Each node contains metadata (of type &lt;code&gt;FileMetadata&lt;/code&gt;) and an array of child nodes (&lt;code&gt;children&lt;/code&gt;). The &lt;code&gt;print&lt;/code&gt; method allows us to print the path of the node and its children, with indentation to represent the depth in the tree.&lt;/p&gt;
&lt;p&gt;We&amp;rsquo;ll need a method to hash a file&amp;rsquo;s content. For this, we use Node.js&amp;rsquo;s built-in &lt;code&gt;crypto&lt;/code&gt; module:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;hashFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fileBuffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;promises&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;hashSum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;crypto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;createHash&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;sha256&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;hashSum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fileBuffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;hashSum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;hex&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;hashFile&lt;/code&gt; function reads a file without blocking the event loop, then computes a SHA-256 digest. For very large files, use a stream instead of buffering the entire file.&lt;/p&gt;
&lt;p&gt;Finally, we create the &lt;code&gt;walkDir&lt;/code&gt; function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;walkDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentPath&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;promises&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lstat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentPath&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;stat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isSymbolicLink&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Symbolic links are not followed: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;currentPath&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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;stat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isDirectory&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;path&lt;/span&gt;: &lt;span class="kt"&gt;currentPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;isDirectory&lt;/span&gt;: &lt;span class="kt"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;hash&lt;/span&gt;: &lt;span class="kt"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;hashFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentPath&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;entries&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;promises&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;readdir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;withFileTypes&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;all&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;entry&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;walkDir&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;TreeNode&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;: &lt;span class="kt"&gt;currentPath&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isDirectory&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;children&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The traversal rejects symbolic links instead of following them outside the requested tree or entering a cycle. Decide whether permission and disappearance errors should stop the walk or be represented in the result. &lt;code&gt;Promise.all&lt;/code&gt; can open too many files in a very large tree, so production crawlers should add a concurrency limit.&lt;/p&gt;
&lt;p&gt;This produces a useful tree for a controlled directory. It is not a replacement for a version-control object model: files can change during the walk, metadata is platform-dependent, and a stable snapshot requires additional consistency rules.&lt;/p&gt;</description></item><item><title>Use IAM Identity Center Profiles Safely from TypeScript</title><link>https://www.yopa.page/blog/2026-08-01-iam-identity-center-with-typescript.html</link><pubDate>Tue, 23 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-iam-identity-center-with-typescript.html</guid><description>
&lt;p&gt;AWS Single Sign-On was renamed &lt;strong&gt;AWS IAM Identity Center&lt;/strong&gt;. More importantly, application code no longer needs to launch &lt;code&gt;aws sso login&lt;/code&gt; through a shell or extract credentials itself. AWS CLI v2 performs the interactive login; the AWS SDK for JavaScript v3 resolves and refreshes the resulting temporary credentials.&lt;/p&gt;
&lt;h2 id="configure-and-authenticate"&gt;
&lt;a href="#configure-and-authenticate" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Configure and authenticate
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws configure sso --profile engineering-dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws sso login --profile engineering-dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws sts get-caller-identity --profile engineering-dev
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Modern CLI configuration normally contains a reusable &lt;code&gt;sso-session&lt;/code&gt; section plus a profile that selects the account and permission set. Let the CLI write this structure. Do not depend on its exact cache-file layout: it is an implementation detail, can contain multiple sessions, and is not an application API.&lt;/p&gt;
&lt;p&gt;Login is deliberately interactive. Keep it outside a server process and outside a web request handler. A program that interpolates an untrusted profile name into &lt;code&gt;exec(&amp;quot;aws sso login ...&amp;quot;)&lt;/code&gt; also creates command-injection risk.&lt;/p&gt;
&lt;h2 id="use-the-v3-provider-chain"&gt;
&lt;a href="#use-the-v3-provider-chain" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Use the v3 provider chain
&lt;/h2&gt;
&lt;p&gt;Install only the service client you use and the credential provider package:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm install @aws-sdk/client-sts @aws-sdk/credential-providers
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;STSClient&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;GetCallerIdentityCommand&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@aws-sdk/client-sts&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fromIni&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@aws-sdk/credential-providers&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;AWS_PROFILE&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;engineering-dev&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;STSClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;region&lt;/span&gt;: &lt;span class="kt"&gt;process.env.AWS_REGION&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;us-east-1&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;credentials&lt;/span&gt;: &lt;span class="kt"&gt;fromIni&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;profile&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;identity&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;sts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;GetCallerIdentityCommand&lt;/span&gt;&lt;span class="p"&gt;({}));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;account&lt;/span&gt;: &lt;span class="kt"&gt;identity.Account&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;arn&lt;/span&gt;: &lt;span class="kt"&gt;identity.Arn&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;fromIni&lt;/code&gt; supports profiles that use IAM Identity Center and profiles that assume another role from that source. For a simple service client, the Node.js default provider chain is often enough: set &lt;code&gt;AWS_PROFILE&lt;/code&gt; and omit &lt;code&gt;credentials&lt;/code&gt; entirely. &lt;code&gt;fromSSO&lt;/code&gt; is useful when the selected profile itself is an SSO profile, but &lt;code&gt;fromIni&lt;/code&gt; is more flexible for role chaining.&lt;/p&gt;
&lt;p&gt;Never log the resolved credential object. The access key, secret key, and session token are short-lived, but still authorize requests until they expire.&lt;/p&gt;
&lt;h2 id="local-people-and-deployed-workloads-are-different"&gt;
&lt;a href="#local-people-and-deployed-workloads-are-different" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Local people and deployed workloads are different
&lt;/h2&gt;
&lt;p&gt;Identity Center profiles are intended for workforce tools and local development. Code deployed to Lambda, ECS, EC2, or EKS should use its workload role through the default provider chain. CI should prefer OIDC federation. A server must not depend on a developer&amp;rsquo;s browser session or home-directory cache.&lt;/p&gt;
&lt;h2 id="failure-handling"&gt;
&lt;a href="#failure-handling" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Failure handling
&lt;/h2&gt;
&lt;p&gt;If the session expired, the SDK error should tell the operator to run &lt;code&gt;aws sso login --profile ...&lt;/code&gt;; the application should not silently switch to another profile. Validate the resolved account before destructive operations. Avoid &lt;code&gt;default&lt;/code&gt; for scripts that can change infrastructure, because an implicit profile makes account mistakes harder to see.&lt;/p&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Upgrade from the monolithic &lt;code&gt;aws-sdk&lt;/code&gt; v2 package to modular v3 clients.&lt;/li&gt;
&lt;li&gt;Replace embedded keys and custom cache parsing with the default provider chain or &lt;code&gt;fromIni&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Move interactive login to an explicit operator step.&lt;/li&gt;
&lt;li&gt;Validate account and ARN with STS before changes.&lt;/li&gt;
&lt;li&gt;Replace shell-string execution with SDK calls; if a CLI process is unavoidable, use argument arrays and an allowlisted profile.&lt;/li&gt;
&lt;li&gt;Test expiry and re-login behavior, role chaining, and a wrong-account guard.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tradeoff: profile-based authentication is convenient for local tools, but it requires AWS CLI v2 and an interactive session. Workload roles are non-interactive and better for deployment, while OIDC is better for external automation.&lt;/p&gt;
&lt;h2 id="account-guard-for-mutating-tools"&gt;
&lt;a href="#account-guard-for-mutating-tools" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Account guard for mutating tools
&lt;/h2&gt;
&lt;p&gt;For a deployment or cleanup utility, make the expected account an explicit input and stop before the first mutation when it differs. This protects against a valid session for the wrong account—a failure that credential refresh cannot detect.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expectedAccount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;EXPECTED_AWS_ACCOUNT_ID&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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;expectedAccount&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Account&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;expectedAccount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sb"&gt;`Refusing to continue: expected &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;expectedAccount&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;, resolved &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;identity&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Account&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Also test three negative paths: no cached session, a profile that is configured but not authorized for the account, and a permission set that can authenticate but cannot perform the service action. Authentication success and authorization success are separate facts. When reporting failure, include the non-sensitive profile, account, role ARN, Region, AWS request ID, and error name; redact tokens and signed headers.&lt;/p&gt;
&lt;p&gt;For long-running local processes, keep the provider function attached to each SDK client so it can refresh. Resolving it once at startup and converting it into a plain credential object discards that advantage. If the process must run unattended for days, a human SSO session is the wrong operational identity; move it to a managed workload or an external workload federation flow.&lt;/p&gt;
&lt;p&gt;Verified on &lt;strong&gt;2026-08-01&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="official-sources"&gt;
&lt;a href="#official-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Official sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sso.html"&gt;IAM Identity Center authentication in the AWS CLI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/sdk-for-javascript/v3/developer-guide/migrate-credential-providers.html"&gt;AWS SDK for JavaScript v3 credential providers&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/Package/-aws-sdk-credential-providers/"&gt;&lt;code&gt;@aws-sdk/credential-providers&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>AWS Access in 2026: Federation and Temporary Credentials First</title><link>https://www.yopa.page/blog/2026-08-01-aws-access-with-temporary-credentials.html</link><pubDate>Mon, 22 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-aws-access-with-temporary-credentials.html</guid><description>
&lt;p&gt;The useful question is no longer “IAM user or IAM role?” It is “who needs access, where does the code run, and which temporary credential mechanism fits that boundary?”&lt;/p&gt;
&lt;p&gt;AWS currently recommends temporary credentials for both people and workloads. IAM users still exist, but they are an exception for cases that cannot use federation or roles—not the normal starting point.&lt;/p&gt;
&lt;h2 id="the-current-mental-model"&gt;
&lt;a href="#the-current-mental-model" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The current mental model
&lt;/h2&gt;
&lt;p&gt;An IAM user is a long-lived identity inside one AWS account. Its password and access keys remain usable until they are rotated or revoked. That persistence increases the cost of accidental disclosure.&lt;/p&gt;
&lt;p&gt;An IAM role has permissions and a trust policy but no long-lived credentials. When a person, workload, or AWS service assumes it, AWS Security Token Service issues an access key, secret key, and session token that expire. The trust policy answers who may assume the role; its permissions answer what an assumed session may do.&lt;/p&gt;
&lt;p&gt;Use these defaults:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Workforce access:&lt;/strong&gt; federate through AWS IAM Identity Center or another identity provider. Assign permission sets to groups, not one-off user policies.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;AWS workloads:&lt;/strong&gt; attach a service role to Lambda, ECS tasks, EC2 instances, or other compute. Let the AWS SDK default credential provider discover it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;External CI/CD:&lt;/strong&gt; use workload identity federation such as GitHub Actions OIDC to assume a narrowly scoped role.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-account access:&lt;/strong&gt; assume a role in the target account, with explicit trust and conditions.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unsupported legacy workload:&lt;/strong&gt; use an IAM user only after ruling out roles and federation. Scope and rotate its keys, monitor their use, and plan its removal.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="a-safe-local-workflow"&gt;
&lt;a href="#a-safe-local-workflow" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A safe local workflow
&lt;/h2&gt;
&lt;p&gt;Configure IAM Identity Center with AWS CLI v2:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws configure sso --profile engineering-dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws sso login --profile engineering-dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;aws sts get-caller-identity --profile engineering-dev
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The last command is an important verification step: confirm the account and ARN before making changes. Applications should select the profile and let the SDK resolve temporary credentials. Do not copy the generated access key into source code or &lt;code&gt;.env&lt;/code&gt; files.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;AWS_PROFILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;engineering-dev aws s3api list-buckets
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;For an AWS-hosted workload, do not run &lt;code&gt;aws configure&lt;/code&gt; in the container. Attach an execution or task role and construct SDK clients without explicit keys.&lt;/p&gt;
&lt;h2 id="permission-design"&gt;
&lt;a href="#permission-design" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Permission design
&lt;/h2&gt;
&lt;p&gt;Temporary credentials reduce credential lifetime; they do not make broad permissions safe. Start with the access needed to get a workload running, then use IAM Access Analyzer and observed activity to tighten it. Add conditions where they meaningfully constrain the request: organization ID, source account, resource tags, or a specific OIDC subject.&lt;/p&gt;
&lt;p&gt;Require phishing-resistant MFA where available for human access. Protect the root user separately and do not create root access keys. Review unused roles, permission sets, policies, and credentials on a schedule.&lt;/p&gt;
&lt;h2 id="tradeoffs"&gt;
&lt;a href="#tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Tradeoffs
&lt;/h2&gt;
&lt;p&gt;Identity Center adds central configuration and depends on a working identity source, but it gives one place for account assignments and short-lived sessions. Service roles are easy on AWS compute but do not directly solve access from arbitrary external hosts. OIDC removes stored CI secrets but requires careful audience and subject conditions. IAM users can support a small set of legacy integrations, at the cost of rotation, storage, monitoring, and incident-response work.&lt;/p&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Inventory IAM users, access keys, last-used data, and their real owners.&lt;/li&gt;
&lt;li&gt;Classify each identity as human, AWS workload, external workload, or break-glass use.&lt;/li&gt;
&lt;li&gt;Create permission sets or roles with equivalent, preferably narrower, permissions.&lt;/li&gt;
&lt;li&gt;Test with &lt;code&gt;sts get-caller-identity&lt;/code&gt; and representative read/write operations.&lt;/li&gt;
&lt;li&gt;Update applications to use the default provider chain rather than explicit keys.&lt;/li&gt;
&lt;li&gt;Monitor CloudTrail during a defined overlap period.&lt;/li&gt;
&lt;li&gt;Deactivate an old key before deleting it; roll back only if a verified dependency still uses it.&lt;/li&gt;
&lt;li&gt;Remove unused IAM users and document any justified exception.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Verified against official documentation on &lt;strong&gt;2026-08-01&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="official-sources"&gt;
&lt;a href="#official-sources" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Official sources
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/best-practices.html"&gt;Security best practices in IAM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/introduction_identity-management.html"&gt;Compare IAM identities and credentials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/singlesignon/latest/userguide/what-is.html"&gt;AWS IAM Identity Center User Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/access-analyzer-policy-generation.html"&gt;IAM Access Analyzer policy generation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>The Observer Pattern</title><link>https://www.yopa.page/blog/2023-05-21-the-observer-pattern.html</link><pubDate>Sun, 21 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-21-the-observer-pattern.html</guid><description>
&lt;h1 id="understanding-the-observer-pattern-in-typescript-a-comprehensive-guide"&gt;
&lt;a href="#understanding-the-observer-pattern-in-typescript-a-comprehensive-guide" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Understanding the Observer Pattern in TypeScript: A Comprehensive Guide
&lt;/h1&gt;
&lt;p&gt;The Observer pattern is a design pattern in which an object, known as the &lt;strong&gt;subject&lt;/strong&gt;, maintains a list of its dependents, called &lt;strong&gt;observers&lt;/strong&gt;, and notifies them automatically of any state changes, typically by calling one of their methods.&lt;/p&gt;
&lt;p&gt;This pattern is particularly useful when you want to ensure multiple parts of your application are notified when another part changes. It fosters a degree of loose coupling between different parts of an application.&lt;/p&gt;
&lt;h2 id="how-does-the-observer-pattern-work"&gt;
&lt;a href="#how-does-the-observer-pattern-work" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
How does the Observer Pattern Work?
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s break down the main components of the Observer pattern:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Subject&lt;/strong&gt;: The subject holds the state and oversees the observers. It provides the methods to register and unregister observers. Moreover, it has a method to notify all observers of a state change.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Observer&lt;/strong&gt;: Observers are objects that follow the state of the subject. They expose a method that gets called when the subject&amp;rsquo;s state changes.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Concrete Subject&lt;/strong&gt;: It&amp;rsquo;s the object having the state that other objects are interested in. It extends the Subject class.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Concrete Observer&lt;/strong&gt;: These are the real objects that are observing the state. They extend the Observer class.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="example-with-typescript"&gt;
&lt;a href="#example-with-typescript" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Example with TypeScript
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;span class="lnt"&gt;42
&lt;/span&gt;&lt;span class="lnt"&gt;43
&lt;/span&gt;&lt;span class="lnt"&gt;44
&lt;/span&gt;&lt;span class="lnt"&gt;45
&lt;/span&gt;&lt;span class="lnt"&gt;46
&lt;/span&gt;&lt;span class="lnt"&gt;47
&lt;/span&gt;&lt;span class="lnt"&gt;48
&lt;/span&gt;&lt;span class="lnt"&gt;49
&lt;/span&gt;&lt;span class="lnt"&gt;50
&lt;/span&gt;&lt;span class="lnt"&gt;51
&lt;/span&gt;&lt;span class="lnt"&gt;52
&lt;/span&gt;&lt;span class="lnt"&gt;53
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Subject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;registerObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;: &lt;span class="kt"&gt;Observer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;removeObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;: &lt;span class="kt"&gt;Observer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;notifyObservers&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Observer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;temperature&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;WeatherStation&lt;/span&gt; &lt;span class="kr"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;Subject&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;temperature&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;observers&lt;/span&gt;: &lt;span class="kt"&gt;Observer&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;setTemperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;temp&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;WeatherStation: new temperature measurement: &amp;#39;&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;temperature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;notifyObservers&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;registerObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;: &lt;span class="kt"&gt;Observer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;removeObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;: &lt;span class="kt"&gt;Observer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;o&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;splice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;notifyObservers() {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;observer&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;observers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;observer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;temperature&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;TemperatureDisplay&lt;/span&gt; &lt;span class="kr"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;Observer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;subject&lt;/span&gt;: &lt;span class="kt"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;weatherStation&lt;/span&gt;: &lt;span class="kt"&gt;Subject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;weatherStation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;weatherStation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;registerObserver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;temperature&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;TemperatureDisplay: I need to update my display&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Logic would go here
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;weatherStation&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;WeatherStation&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;tempDisplay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;TemperatureDisplay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;weatherStation&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;weatherStation&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;setTemperature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In the example above, the &lt;code&gt;WeatherStation&lt;/code&gt; is the subject that maintains the list of observers and notifies them of any changes. &lt;code&gt;TemperatureDisplay&lt;/code&gt; is the observer that gets notified of the changes.&lt;/p&gt;
&lt;p&gt;The observer pattern is useful when several consumers need notification of state changes. Define unsubscribe and error behavior explicitly so observers do not leak resources or silently stop receiving events.&lt;/p&gt;</description></item><item><title>The Decorator Pattern</title><link>https://www.yopa.page/blog/2023-05-20-the-decorator-pattern.html</link><pubDate>Sat, 20 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-20-the-decorator-pattern.html</guid><description>
&lt;h1 id="the-decorator-pattern-with-typescript"&gt;
&lt;a href="#the-decorator-pattern-with-typescript" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Decorator Pattern with TypeScript
&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;The Decorator Pattern&lt;/code&gt; is one of the most powerful &lt;strong&gt;structural design pattern&lt;/strong&gt;s in the software development arsenal, granting flexibility and dynamism in extending object behavior.&lt;/p&gt;
&lt;h2 id="what-is-the-decorator-pattern"&gt;
&lt;a href="#what-is-the-decorator-pattern" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What is the Decorator Pattern?
&lt;/h2&gt;
&lt;p&gt;The Decorator Pattern is a design pattern that allows behavior to be added to individual objects, either statically or dynamically, without affecting the behavior of other objects from the same class.
&lt;strong&gt;Decorators provide a flexible alternative to subclassing for extending functionality.&lt;/strong&gt;&lt;/p&gt;
&lt;h2 id="when-should-you-use-the-decorator-pattern"&gt;
&lt;a href="#when-should-you-use-the-decorator-pattern" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
When Should You Use the Decorator Pattern?
&lt;/h2&gt;
&lt;p&gt;The Decorator Pattern provides an alternative to subclassing, which involves creating a new class for every new behavior.&lt;/p&gt;
&lt;h3 id="use-the-decorator-pattern-when"&gt;
&lt;a href="#use-the-decorator-pattern-when" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Use the Decorator Pattern when:
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;You want to add responsibilities to individual objects dynamically and transparently, that is, without affecting other objects.&lt;/li&gt;
&lt;li&gt;You want to add responsibilities to an object that you may want to change in the future.&lt;/li&gt;
&lt;li&gt;Extending functionality by subclassing is impractical because it leads to a large number of subclasses and complicates code maintenance and error tracking.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="example-with-typescript"&gt;
&lt;a href="#example-with-typescript" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Example with TypeScript
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s assume we are developing a &lt;code&gt;text editor&lt;/code&gt;, and we want to have the ability to add various types of formatting to the text, such as &lt;code&gt;bold&lt;/code&gt;, &lt;code&gt;italic&lt;/code&gt;, or &lt;code&gt;underline&lt;/code&gt;. Here is how we can implement this using the Decorator Pattern:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Base Component
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Text&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;displayText&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Concrete Component
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;PlainText&lt;/span&gt; &lt;span class="kr"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;Text&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;displayText&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Plain Text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Base Decorator
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;TextDecorator&lt;/span&gt; &lt;span class="kr"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;Text&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;protected&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;: &lt;span class="kt"&gt;Text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;displayText&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;displayText&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Concrete Decorators
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;BoldText&lt;/span&gt; &lt;span class="kr"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;TextDecorator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;displayText&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sb"&gt;`Bold(&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;displayText&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;ItalicText&lt;/span&gt; &lt;span class="kr"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;TextDecorator&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;displayText&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sb"&gt;`Italic(&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="kr"&gt;super&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;displayText&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Usage
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;: &lt;span class="kt"&gt;Text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;PlainText&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;BoldText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;ItalicText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;displayText&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// Outputs: Italic(Bold(Plain Text))
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, we&amp;rsquo;re able to apply multiple formatting options (bold, italic) to our text dynamically, and we can easily introduce more (like underline, strikethrough, etc.) without affecting existing classes.&lt;/p&gt;
&lt;h2 id="wrapping-up"&gt;
&lt;a href="#wrapping-up" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Wrapping up
&lt;/h2&gt;
&lt;p&gt;The Decorator Pattern is a highly effective tool in a developer&amp;rsquo;s toolbox, offering dynamic and flexible object behavior. It aligns well with the open-closed principle, &lt;strong&gt;one of the five principles of SOLID&lt;/strong&gt;, leading to more maintainable and less error-prone code. Used appropriately and sparingly, the Decorator Pattern can greatly improve your code design and structure.&lt;/p&gt;</description></item><item><title>Strategy Pattern in Dependency Injection</title><link>https://www.yopa.page/blog/2023-05-19-strategy-pattern-in-dependency-injection.html</link><pubDate>Fri, 19 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-19-strategy-pattern-in-dependency-injection.html</guid><description>
&lt;h2 id="understanding-the-concepts"&gt;
&lt;a href="#understanding-the-concepts" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Understanding the Concepts
&lt;/h2&gt;
&lt;h3 id="strategy-pattern"&gt;
&lt;a href="#strategy-pattern" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Strategy Pattern
&lt;/h3&gt;
&lt;p&gt;The Strategy Pattern is a behavioral design pattern that encapsulates algorithms into separate classes with a common interface, enabling interchangeability within the original context object. This approach enhances flexibility by allowing the switching of algorithms or logic at runtime.&lt;/p&gt;
&lt;h3 id="dependency-injection"&gt;
&lt;a href="#dependency-injection" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Dependency Injection
&lt;/h3&gt;
&lt;p&gt;Dependency Injection is a technique that promotes Inversion of Control (IoC) - an object obtains its dependencies from an external source rather than creating them itself. DI is instrumental in developing code that is more manageable, modular, and testable.&lt;/p&gt;
&lt;h2 id="bridging-strategy-pattern-with-dependency-injection"&gt;
&lt;a href="#bridging-strategy-pattern-with-dependency-injection" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Bridging Strategy Pattern with Dependency Injection
&lt;/h2&gt;
&lt;p&gt;When combined, these two concepts can provide exceptional benefits. &lt;code&gt;Dependency Injection&lt;/code&gt; can be used to inject different Strategy objects into a class, thus promoting flexible behavior that varies with the injected strategy.&lt;/p&gt;
&lt;h2 id="a-practical-example"&gt;
&lt;a href="#a-practical-example" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A Practical Example
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s consider a simple example: a &lt;code&gt;TextFormatter&lt;/code&gt; class that formats a given text in different ways, such as lower case, upper case, or capitalized. We&amp;rsquo;ll use a Python-esque pseudo-code for our demonstration.&lt;/p&gt;
&lt;p&gt;Firstly, we define the strategy interface and the concrete strategies.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TextFormatStrategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;format_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;LowerCaseStrategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TextFormatStrategy&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;format_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UpperCaseStrategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TextFormatStrategy&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;format_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;upper&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CapitalizeStrategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TextFormatStrategy&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;format_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;capitalize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Next, we define the &lt;code&gt;TextFormatter&lt;/code&gt; class which will use &lt;code&gt;Dependency Injection&lt;/code&gt; to accept a &lt;code&gt;TextFormatStrategy&lt;/code&gt; object.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TextFormatter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;TextFormatStrategy&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_strategy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now we can change the behavior of the &lt;code&gt;TextFormatter&lt;/code&gt; class at runtime by injecting different strategies.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;formatter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TextFormatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;LowerCaseStrategy&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formatter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Hello World&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;# Output: &amp;#34;hello world&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;formatter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TextFormatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;UpperCaseStrategy&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formatter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Hello World&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;# Output: &amp;#34;HELLO WORLD&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;formatter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;TextFormatter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CapitalizeStrategy&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;formatter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;hello world&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;# Output: &amp;#34;Hello world&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This example demonstrates how &lt;strong&gt;Dependency Injection&lt;/strong&gt; can inject different &lt;strong&gt;strategies&lt;/strong&gt; into a class, resulting in dynamic behavior changes based on the provided strategy.&lt;/p&gt;</description></item><item><title>--save-dev in npm</title><link>https://www.yopa.page/blog/2023-05-18---save-dev-in-npm.html</link><pubDate>Thu, 18 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-18---save-dev-in-npm.html</guid><description>
&lt;p&gt;When working with npm (Node Package Manager) to manage dependencies in a Node.js project, the &lt;code&gt;--save-dev&lt;/code&gt; flag is often used to save packages as development dependencies. It is an essential feature that helps distinguish between packages required for development purposes and those needed for the production environment.&lt;/p&gt;
&lt;h2 id="what-does---save-dev-do"&gt;
&lt;a href="#what-does---save-dev-do" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What does &lt;code&gt;--save-dev&lt;/code&gt; do?
&lt;/h2&gt;
&lt;p&gt;By appending &lt;code&gt;--save-dev&lt;/code&gt; to an npm install command, you specify that the package being installed is only required during development and should not be included when deploying your application to a production environment. The package will be added to the &lt;code&gt;devDependencies&lt;/code&gt; section in your &lt;code&gt;package.json&lt;/code&gt; file.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm install &amp;lt;package-name&amp;gt; --save-dev
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;devDependencies&lt;/code&gt; section typically includes packages related to testing, building, and development tooling, such as test frameworks (e.g., Mocha or Jest), build tools (e.g., Gulp or Webpack), linters (e.g., ESLint or Prettier), and other utilities.&lt;/p&gt;
&lt;h2 id="example-use-cases-for---save-dev"&gt;
&lt;a href="#example-use-cases-for---save-dev" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Example use cases for &lt;code&gt;--save-dev&lt;/code&gt;
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Testing frameworks: When writing tests for your application, you often rely on specific testing frameworks like Mocha or Jest. These frameworks are not needed in production but are crucial during development and continuous integration processes. By installing them with &lt;code&gt;--save-dev&lt;/code&gt;, you ensure they are available when running tests but won&amp;rsquo;t unnecessarily bloat your production build.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Build tools: Build tools such as Gulp, Grunt, or Webpack are commonly employed during development to compile and bundle assets, optimize code, or perform other build-related tasks. These tools are not required in a production environment, as the bundled code can be executed without them. Therefore, it&amp;rsquo;s advisable to save them as development dependencies using &lt;code&gt;--save-dev&lt;/code&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Linters and code formatters: Linters, such as ESLint, and code formatters, like Prettier, assist in maintaining consistent code quality and style. They provide valuable feedback during development but are not necessary for the deployed application. Including them as &lt;code&gt;devDependencies&lt;/code&gt; allows developers to use them while working on the codebase, without affecting the production build.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="when-not-to-use---save-dev"&gt;
&lt;a href="#when-not-to-use---save-dev" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
When not to use &lt;code&gt;--save-dev&lt;/code&gt;
&lt;/h2&gt;
&lt;p&gt;Although &lt;code&gt;--save-dev&lt;/code&gt; is useful in many scenarios, there are situations where it should not be employed. One example is when installing packages that are explicitly required at runtime by your application. These packages provide functionality that is essential for the application to run correctly, regardless of the development or production context.&lt;/p&gt;
&lt;p&gt;For instance, consider a web server framework like Express. Express is a core dependency for a Node.js web application and is necessary for the application to function correctly. In this case, you should install Express without using &lt;code&gt;--save-dev&lt;/code&gt;, as it belongs to the regular dependencies section of your &lt;code&gt;package.json&lt;/code&gt; file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm install express
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;By installing Express as a regular dependency, it will be bundled and deployed along with your application to ensure proper execution in both development and production environment.&lt;/p&gt;</description></item><item><title>The Strategy Pattern</title><link>https://www.yopa.page/blog/2023-05-17-the-strategy-pattern.html</link><pubDate>Wed, 17 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-17-the-strategy-pattern.html</guid><description>
&lt;p&gt;The ability to modify or extend functionality without impacting the existing codebase is crucial in a rapidly evolving technological landscape. One design pattern that helps achieve this goal is &lt;code&gt;the Strategy Pattern&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="understanding-the-strategy-pattern"&gt;
&lt;a href="#understanding-the-strategy-pattern" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Understanding the Strategy Pattern
&lt;/h2&gt;
&lt;p&gt;The Strategy Pattern is &lt;strong&gt;a behavioral design pattern&lt;/strong&gt; that enables the definition of a family of algorithms, encapsulates each one as a separate class, and makes them interchangeable at runtime. It allows the algorithm to vary independently from the clients that use it, promoting &lt;code&gt;loose coupling&lt;/code&gt; between objects and providing a way to select algorithms dynamically.&lt;/p&gt;
&lt;p&gt;At the heart of the Strategy Pattern are three main components:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Context&lt;/strong&gt;: This is the class that contains the main business logic and maintains a reference to the chosen strategy object. It provides a method for clients to set the strategy or change it dynamically during runtime.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Strategy&lt;/strong&gt;: The strategy interface defines a common set of methods that encapsulate different algorithms or behaviors. Each strategy represents a concrete implementation of a particular algorithm.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Concrete Strategies&lt;/strong&gt;: These are the classes that implement the strategy interface and provide specific implementations of the algorithms or behaviors.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="advantages-of-the-strategy-pattern"&gt;
&lt;a href="#advantages-of-the-strategy-pattern" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Advantages of the Strategy Pattern
&lt;/h2&gt;
&lt;p&gt;The Strategy Pattern offers several benefits in software design:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Improved Code Reusability
By encapsulating algorithms within separate strategy classes, the Strategy Pattern promotes code reuse. Different parts of the system can utilize the same set of strategies, enhancing maintainability and reducing code duplication. This reusability simplifies future modifications or additions to the system.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enhanced Flexibility and Extensibility
The Strategy Pattern allows for easy extension and modification of behaviors. Adding a new strategy involves creating a new class that implements the strategy interface, without needing to modify existing code. This flexibility enables dynamic selection of strategies at runtime, enabling the system to adapt to changing requirements.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Separation of Concerns
The Strategy Pattern separates the logic of an algorithm from the context that uses it. This separation promotes cleaner code architecture by assigning responsibilities to appropriate classes. The context class focuses on the main business logic, while the strategies encapsulate specific algorithms. It enhances the overall maintainability and readability of the codebase.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Improved Testability
Because the Strategy Pattern encapsulates algorithms within separate classes, it becomes easier to test each strategy independently. By isolating the behavior in smaller, self-contained units, unit testing and mocking become more straightforward, resulting in better test coverage and more robust software.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Runtime Selection of Algorithms
One of the key advantages of the Strategy Pattern is the ability to select algorithms dynamically during runtime. The context can change the strategy it uses without affecting the clients or requiring complex conditional logic. This dynamic behavior selection provides a powerful mechanism for adapting the system&amp;rsquo;s behavior to different scenarios or user preferences.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="example"&gt;
&lt;a href="#example" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Example
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s consider an example where we want to implement different sorting algorithms for a list of integers using the &lt;strong&gt;Strategy Pattern&lt;/strong&gt;. We&amp;rsquo;ll have a Sorter class as the context, a &lt;code&gt;SortStrategy&lt;/code&gt; interface as the strategy, and multiple concrete strategy classes for different sorting algorithms.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;span class="lnt"&gt;39
&lt;/span&gt;&lt;span class="lnt"&gt;40
&lt;/span&gt;&lt;span class="lnt"&gt;41
&lt;/span&gt;&lt;span class="lnt"&gt;42
&lt;/span&gt;&lt;span class="lnt"&gt;43
&lt;/span&gt;&lt;span class="lnt"&gt;44
&lt;/span&gt;&lt;span class="lnt"&gt;45
&lt;/span&gt;&lt;span class="lnt"&gt;46
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-py" data-lang="py"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Step 1: Define the strategy interface&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SortStrategy&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;pass&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Step 2: Implement concrete strategy classes&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;BubbleSortStrategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SortStrategy&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Implementation of bubble sort algorithm&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Sorting using Bubble Sort&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;QuickSortStrategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SortStrategy&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Implementation of quicksort algorithm&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Sorting using Quick Sort&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MergeSortStrategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;SortStrategy&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# Implementation of merge sort algorithm&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Sorting using Merge Sort&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;# ...&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Step 3: Create the context class&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Sorter&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_strategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;strategy&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;strategy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Step 4: Client code&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;sorter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Sorter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;BubbleSortStrategy&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;sorter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Sorting using Bubble Sort&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;sorter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_strategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;QuickSortStrategy&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;sorter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Sorting using Quick Sort&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;sorter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_strategy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MergeSortStrategy&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;sorter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# Sorting using Merge Sort&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In the above example, we define the &lt;code&gt;SortStrategy&lt;/code&gt; interface that declares the &lt;code&gt;sort()&lt;/code&gt; method. We then implement concrete strategy classes such as &lt;code&gt;BubbleSortStrategy&lt;/code&gt;, &lt;code&gt;QuickSortStrategy&lt;/code&gt;, and &lt;code&gt;MergeSortStrategy&lt;/code&gt;, each providing their own implementation of the &lt;code&gt;sort()&lt;/code&gt; method.&lt;/p&gt;
&lt;p&gt;Next, we create the &lt;code&gt;Sorter&lt;/code&gt; class as the context. It has a reference to the &lt;code&gt;SortStrategy&lt;/code&gt; interface and provides methods to set the strategy dynamically (&lt;code&gt;set_strategy()&lt;/code&gt;) and invoke the sorting operation (&lt;code&gt;sort()&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;In the client code, we instantiate a &lt;code&gt;Sorter&lt;/code&gt; object and initially set it to use the &lt;code&gt;BubbleSortStrategy&lt;/code&gt;. We call the sort() method on the &lt;code&gt;Sorter&lt;/code&gt; object, and it delegates the sorting operation to the currently set strategy (&lt;code&gt;BubbleSortStrategy&lt;/code&gt;).&lt;/p&gt;
&lt;p&gt;We can dynamically change the sorting strategy by calling &lt;code&gt;set_strategy()&lt;/code&gt; with a different strategy object (&lt;code&gt;QuickSortStrategy&lt;/code&gt;, &lt;code&gt;MergeSortStrategy&lt;/code&gt;, etc.). The &lt;code&gt;Sorter&lt;/code&gt; object will then use the new strategy for subsequent sorting operations.&lt;/p&gt;</description></item><item><title>Upsert Salesforce Data from CSV with sf CLI</title><link>https://www.yopa.page/blog/2026-08-01-upsert-salesforce-data-from-csv-with-sf-cli.html</link><pubDate>Tue, 16 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-upsert-salesforce-data-from-csv-with-sf-cli.html</guid><description>
&lt;p&gt;Verified on 2026-08-01. Records are loaded, not deployed, and the retired command&amp;rsquo;s mapping-file flag is not part of the current bulk upsert workflow.&lt;/p&gt;
&lt;p&gt;Prepare UTF-8 CSV with a stable external ID, then use the current command and inspect the job result:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf data upsert bulk --sobject Widget__c --file widgets.csv &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --external-id External_Id__c --target-org staging --wait &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Test a small file first. Keep failed and unprocessed result files, avoid logging sensitive rows, and respect field-level access and validation rules. Bulk is efficient for large asynchronous loads; &lt;code&gt;sf data upsert record&lt;/code&gt; or an API client is simpler for a handful of records and easier to handle transaction by transaction.&lt;/p&gt;
&lt;p&gt;Migration: remove &lt;code&gt;mapping.json&lt;/code&gt;, make CSV headers match API field names, confirm the external-ID field, run in a sandbox, reconcile counts, then promote the same reviewed file and command to the intended org.&lt;/p&gt;
&lt;p&gt;References: &lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-reference/guide/cli_reference_data.html"&gt;Salesforce CLI data commands&lt;/a&gt;, &lt;a href="https://developer.salesforce.com/docs/atlas.en-us.api_asynch.meta/api_asynch/bulk_api_2_0.htm"&gt;Bulk API 2.0&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="current-mental-model"&gt;
&lt;a href="#current-mental-model" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Current mental model
&lt;/h2&gt;
&lt;p&gt;Metadata deployment changes schema and configuration. Data loading creates or changes records and triggers validation rules, Flows, Apex, duplicate rules, sharing, and downstream integrations. An upsert chooses insert or update by an external ID, so the stability and uniqueness of that key determine whether reruns are safe.&lt;/p&gt;
&lt;h2 id="prepare-and-execute-safely"&gt;
&lt;a href="#prepare-and-execute-safely" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Prepare and execute safely
&lt;/h2&gt;
&lt;p&gt;Start with a representative sample that contains no unnecessary sensitive columns. Confirm field API names, data types, picklist values, locale-sensitive dates, UTF-8 encoding, and the external-ID field. Query a few keys first to understand collision behavior.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf data query --target-org staging &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --query &lt;span class="s2"&gt;&amp;#34;SELECT Id, External_Id__c FROM Widget__c LIMIT 5&amp;#34;&lt;/span&gt; --json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf data upsert bulk --sobject Widget__c --file widgets-sample.csv &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --external-id External_Id__c --target-org staging --wait &lt;span class="m"&gt;10&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Reconcile successful, failed, and unprocessed counts with the input. Read failure files instead of blindly rerunning the whole batch. When lookup relationships are involved, load parents first or resolve stable external IDs; do not copy Salesforce record IDs between environments.&lt;/p&gt;
&lt;h2 id="alternatives-and-tradeoffs"&gt;
&lt;a href="#alternatives-and-tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Alternatives and tradeoffs
&lt;/h2&gt;
&lt;p&gt;Bulk API 2.0 minimizes client orchestration for large files but is asynchronous and can produce partial success. Record commands are clearer for small operational changes. A purpose-built integration can provide transformations, retries, and observability but requires ownership and testing. Data Loader remains useful for supervised admin work, though its configuration must be reviewed like code.&lt;/p&gt;
&lt;h2 id="migration-and-verification-checklist"&gt;
&lt;a href="#migration-and-verification-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration and verification checklist
&lt;/h2&gt;
&lt;p&gt;Remove the obsolete mapping flag, rename “deploy” jobs to “load” or “upsert,” and version-control a schema-only sample rather than customer data. Test automation and validation side effects in a sandbox, define retry rules for failed rows, take an appropriate backup, and document rollback. After loading, compare counts, sample fields by external ID, inspect automation failures, and rerun the sample to prove idempotency.&lt;/p&gt;</description></item><item><title>git diff with Two Tags using simple-git TypeScript</title><link>https://www.yopa.page/blog/2023-05-15-git-diff-with-two-tags-using-simple-git-library-in-typescript.html</link><pubDate>Mon, 15 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-15-git-diff-with-two-tags-using-simple-git-library-in-typescript.html</guid><description>
&lt;h2 id="repository-and-reference-safety"&gt;
&lt;a href="#repository-and-reference-safety" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Repository and reference safety
&lt;/h2&gt;
&lt;p&gt;Treat repository URLs, refs, and output paths as untrusted input. Use a unique temporary directory, pass refs as separate arguments, validate that they resolve to commits, and remove the directory in &lt;code&gt;finally&lt;/code&gt;. Confirm the installed &lt;code&gt;simple-git&lt;/code&gt; version because its TypeScript return types have changed across releases.&lt;/p&gt;
&lt;h2 id="performing-git-diff-with-simple-git"&gt;
&lt;a href="#performing-git-diff-with-simple-git" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Performing Git Diff with simple-git
&lt;/h2&gt;
&lt;p&gt;First, make sure you have the simple-git library imported in your TypeScript file:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;simpleGit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;SimpleGit&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;simple-git&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Create a function that will perform the &lt;code&gt;Git diff&lt;/code&gt; operation between two tags. The function should accept the tag names as parameters and return a promise that resolves to the diff output string.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getGitDiff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tag1&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tag2&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;git&lt;/span&gt;: &lt;span class="kt"&gt;SimpleGit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;simpleGit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;revparse&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--verify&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tag1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;^{commit}`&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;revparse&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--verify&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tag2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;^{commit}`&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;tag1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tag2&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The refs are verified as commits before they are passed as separate &lt;code&gt;git diff&lt;/code&gt; arguments. Current &lt;code&gt;simple-git&lt;/code&gt; releases return the textual diff from this overload. Pin the package version and compile the example because older releases exposed different typings.&lt;/p&gt;
&lt;p&gt;Now you can call the getGitDiff function and retrieve the diff output between two tags:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tag1&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;v1.0.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tag2&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;v1.1.0&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;diff&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;getGitDiff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tag1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tag2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="use-case-clone-the-repo-to-local-drive-then-perform-git-diff"&gt;
&lt;a href="#use-case-clone-the-repo-to-local-drive-then-perform-git-diff" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Use case: clone the repo to local drive then perform git diff
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;simpleGit&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;simple-git&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;mkdtemp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rm&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;node:fs/promises&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;tmpdir&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;node:os&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;join&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;node:path&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;gitDiff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;repositoryUrl&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;tag1&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;tag2&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;tempDir&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;mkdtemp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tmpdir&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;tag-diff-&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;simpleGit&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;clone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;repositoryUrl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tempDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--no-checkout&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;git&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;simpleGit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tempDir&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;revparse&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--verify&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tag1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;^{commit}`&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;revparse&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;--verify&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;tag2&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;^{commit}`&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;git&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;diff&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="nx"&gt;tag1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;tag2&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;finally&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rm&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tempDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;recursive&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;force&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This function is appropriate only when the caller is allowed to clone the supplied repository. Production services should also restrict protocols and hosts, set time and size limits, and avoid logging credentials embedded in a URL.&lt;/p&gt;</description></item><item><title>DTO vs Schema</title><link>https://www.yopa.page/blog/2023-05-14-dto-vs-schema.html</link><pubDate>Sun, 14 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-14-dto-vs-schema.html</guid><description>
&lt;h2 id="data-transfer-object-dto"&gt;
&lt;a href="#data-transfer-object-dto" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Data Transfer Object (DTO)
&lt;/h2&gt;
&lt;p&gt;A DTO is a plain TypeScript/JavaScript object used for transferring data between different layers or components of an application. Its primary purpose is to encapsulate data and provide a clear structure for communication across system boundaries, such as API calls or database interactions.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s consider an example of a UserDTO in TypeScript:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;UserDTO&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;id&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;name&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;email&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this case, the UserDTO interface defines the structure of user data that can be transferred between components or sent over the network. It includes properties like id, name, and email, allowing a consistent and well-defined data representation during communication.&lt;/p&gt;
&lt;p&gt;DTOs help decouple different parts of an application by providing a contract for data exchange. They enable type checking and validation when passing data between components, enhancing code reliability and reducing the chances of data-related bugs.&lt;/p&gt;
&lt;h2 id="schema"&gt;
&lt;a href="#schema" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Schema
&lt;/h2&gt;
&lt;p&gt;A schema, on the other hand, describes the structure, constraints, and relationships of data within an application. It serves as a formal definition for expected data shapes and validation rules. Schemas are commonly employed in libraries or frameworks that handle data validation, serialization, or storage.&lt;/p&gt;
&lt;p&gt;To illustrate the concept of a schema in TypeScript, we can use a popular library called Yup:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;yup&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;yup&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;userSchema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;yup&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kt"&gt;object&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;id&lt;/span&gt;: &lt;span class="kt"&gt;yup.number&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;: &lt;span class="kt"&gt;yup.string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;email&lt;/span&gt;: &lt;span class="kt"&gt;yup.string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;required&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, we create a &lt;code&gt;userSchema&lt;/code&gt; using the &lt;code&gt;Yup&lt;/code&gt; library. It defines the expected structure and validation rules for user data. The schema specifies that the &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt;, and &lt;code&gt;email&lt;/code&gt; fields are required, and the email field should be a valid email address.&lt;/p&gt;
&lt;p&gt;Schemas provide a powerful mechanism for validating data integrity and enforcing consistency within an application. They allow you to define complex validation rules, perform data transformations, and handle error conditions effectively.&lt;/p&gt;
&lt;h2 id="dto-vs-schema"&gt;
&lt;a href="#dto-vs-schema" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
DTO vs Schema
&lt;/h2&gt;
&lt;p&gt;While DTOs and schemas share some similarities, they have distinct purposes within an application.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DTOs focus on transferring data between components or across system boundaries.
&lt;ul&gt;
&lt;li&gt;DTOs excel at enabling clear communication and decoupling between different parts of an application. They ensure consistency and type safety when passing data, reducing potential errors and making code more maintainable.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Schemas concentrate on defining and validating the structure of data within an application.
&lt;ul&gt;
&lt;li&gt;schemas provide a mechanism for formally defining data structures and applying validation rules. They enhance data integrity, enforce constraints, and facilitate robust data handling throughout an application.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Tomorrow is Monday guys. yike.. Good luck!&lt;/p&gt;</description></item><item><title>Spread Operator in TypeScript</title><link>https://www.yopa.page/blog/2023-05-12-spread-operator-in-typescript.html</link><pubDate>Fri, 12 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-12-spread-operator-in-typescript.html</guid><description>
&lt;p&gt;&lt;code&gt;Spead Operator&lt;/code&gt; provides an elegant syntax for copying or combining elements from existing arrays or objects into new ones.&lt;/p&gt;
&lt;h2 id="array-manipulation"&gt;
&lt;a href="#array-manipulation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Array Manipulation
&lt;/h2&gt;
&lt;h3 id="copying-an-array"&gt;
&lt;a href="#copying-an-array" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Copying an Array
&lt;/h3&gt;
&lt;p&gt;One of the most common use cases of the spread operator is to create a shallow copy of an array. Let&amp;rsquo;s consider an example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;originalArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;copiedArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;originalArray&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this code snippet, the spread operator &lt;code&gt;...&lt;/code&gt; is used to spread the elements of the &lt;code&gt;originalArray&lt;/code&gt; into a new array &lt;code&gt;copiedArray&lt;/code&gt;. This ensures that modifying the &lt;code&gt;copiedArray&lt;/code&gt; does not affect the &lt;code&gt;originalArray&lt;/code&gt;, as they are separate instances.&lt;/p&gt;
&lt;h3 id="concatenating-arrays"&gt;
&lt;a href="#concatenating-arrays" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Concatenating Arrays
&lt;/h3&gt;
&lt;p&gt;The spread operator can also be used to concatenate arrays effortlessly. Suppose we have two &lt;code&gt;arrays&lt;/code&gt;, &lt;code&gt;array1&lt;/code&gt; and array2, and we want to combine them into a new array &lt;code&gt;combinedArray&lt;/code&gt;. We can achieve this using the spread operator:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;array1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;array2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;combinedArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;array1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;array2&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The resulting &lt;code&gt;combinedArray&lt;/code&gt; will contain all the elements from &lt;code&gt;array1&lt;/code&gt; followed by all the elements from &lt;code&gt;array2&lt;/code&gt;. This approach is much cleaner and more readable than using traditional methods like &lt;code&gt;Array.concat()&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="adding-or-removing-elements"&gt;
&lt;a href="#adding-or-removing-elements" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Adding or Removing Elements
&lt;/h3&gt;
&lt;p&gt;Using the spread operator, we can easily add or remove elements from an array. Consider the following example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;originalArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;newArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;originalArray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this case, the spread operator is used to spread the elements of the &lt;code&gt;originalArray&lt;/code&gt; and append additional elements &lt;code&gt;4&lt;/code&gt; and &lt;code&gt;5&lt;/code&gt; to create a new array &lt;code&gt;newArray&lt;/code&gt;. Similarly, you can remove elements by excluding them using the spread operator.&lt;/p&gt;
&lt;h2 id="object-manipulation"&gt;
&lt;a href="#object-manipulation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Object Manipulation
&lt;/h2&gt;
&lt;h3 id="copying-an-object"&gt;
&lt;a href="#copying-an-object" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Copying an Object
&lt;/h3&gt;
&lt;p&gt;Just like with arrays, the spread operator can be used to create a shallow copy of an object. Let&amp;rsquo;s see an example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;originalObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;John&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt;: &lt;span class="kt"&gt;30&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;copiedObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;originalObject&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this case, the spread operator is used to spread the properties of the &lt;code&gt;originalObject&lt;/code&gt; into a new object &lt;code&gt;copiedObject&lt;/code&gt;. This ensures that modifying the &lt;code&gt;copiedObject&lt;/code&gt; does not affect the &lt;code&gt;originalObject&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="merging-objects"&gt;
&lt;a href="#merging-objects" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Merging Objects
&lt;/h3&gt;
&lt;p&gt;The spread operator can also be used to merge multiple objects into a new object. Suppose we have two objects, &lt;code&gt;object1&lt;/code&gt; and &lt;code&gt;object2&lt;/code&gt;, and we want to merge them into a new object &lt;code&gt;mergedObject&lt;/code&gt;. We can achieve this using the spread operator:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;object1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;John&amp;#39;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;object2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt;: &lt;span class="kt"&gt;30&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mergedObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;object1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;object2&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The resulting &lt;code&gt;mergedObject&lt;/code&gt; will contain all the properties from &lt;code&gt;object1&lt;/code&gt; and &lt;code&gt;object2&lt;/code&gt;. If there are overlapping properties, the value from &lt;code&gt;object2&lt;/code&gt; will overwrite the value from &lt;code&gt;object1&lt;/code&gt;.&lt;/p&gt;
&lt;h3 id="modifying-object-properties"&gt;
&lt;a href="#modifying-object-properties" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Modifying Object Properties
&lt;/h3&gt;
&lt;p&gt;The spread operator can also be used to modify specific properties of an object while keeping the rest intact. Consider the following example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;originalObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;John&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt;: &lt;span class="kt"&gt;30&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;modifiedObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;originalObject&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt;: &lt;span class="kt"&gt;31&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In the example above, the spread operator is used to spread the properties of the &lt;code&gt;originalObject&lt;/code&gt; into a new object &lt;code&gt;modifiedObject&lt;/code&gt;. The &lt;code&gt;age&lt;/code&gt; property is also included and assigned a new value of &lt;code&gt;31&lt;/code&gt;. This creates a new object with the same properties as the &lt;code&gt;originalObject&lt;/code&gt;, except for the modified property.&lt;/p&gt;
&lt;p&gt;By using the spread operator in this way, you can easily modify specific properties of an object without mutating the original object itself. It provides a clean and concise syntax for object manipulation.&lt;/p&gt;
&lt;h3 id="rest-parameters"&gt;
&lt;a href="#rest-parameters" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Rest Parameters
&lt;/h3&gt;
&lt;p&gt;In addition to array and object manipulation, the spread operator can also be used with function parameters to handle variable-length arguments. This is often referred to as &amp;ldquo;rest parameters&amp;rdquo; in TypeScript.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(...&lt;/span&gt;&lt;span class="nx"&gt;numbers&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;acc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;acc&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: 15
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, the spread operator &lt;code&gt;...&lt;/code&gt; is used in the function parameter declaration &lt;code&gt;...numbers: number[]&lt;/code&gt;. It allows you to pass any number of arguments to the &lt;code&gt;sum&lt;/code&gt; function, and TypeScript collects them into an array called &lt;code&gt;numbers&lt;/code&gt;. The function then performs the sum operation on all the numbers in the array.&lt;/p&gt;
&lt;p&gt;Rest parameters provide flexibility when dealing with functions that need to handle a variable number of arguments. The spread operator simplifies the process of passing and manipulating these arguments.&lt;/p&gt;</description></item><item><title>Virtual Class and Abstract Class in Apex</title><link>https://www.yopa.page/blog/2023-05-13-virtual-class-and-abstract-class-in-apex.html</link><pubDate>Fri, 12 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-13-virtual-class-and-abstract-class-in-apex.html</guid><description>
&lt;p&gt;&lt;code&gt;virtual&lt;/code&gt; and &lt;code&gt;abstract&lt;/code&gt; both allow inheritance in Apex, but they express different contracts. Use them deliberately; an interface or composition is often simpler when callers only need behavior rather than shared state.&lt;/p&gt;
&lt;h2 id="virtual-classes"&gt;
&lt;a href="#virtual-classes" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Virtual Classes
&lt;/h2&gt;
&lt;p&gt;A virtual class in Apex is a class that allows its methods to be overridden by subclasses. It provides a way to define a base implementation while also enabling customization and extension by derived classes. In other words, virtual classes serve as a starting point for creating specialized implementations.&lt;/p&gt;
&lt;p&gt;To declare a &lt;code&gt;virtual&lt;/code&gt; class in Apex, you use the &lt;code&gt;virtual&lt;/code&gt; keyword before the class keyword, like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;virtual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyBaseClass&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Class implementation goes here&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Methods within a virtual class can be marked as virtual as well.&lt;/strong&gt; This indicates that the method can be overridden by subclasses to provide their own implementation. For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;virtual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyBaseClass&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;virtual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;myMethod&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Base implementation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Subclasses of a virtual class can override its methods using the &lt;code&gt;override&lt;/code&gt; keyword. This allows them to provide their own custom implementation. For instance:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;virtual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyBaseClass&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;virtual&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;myMethod&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Base implementation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyDerivedClass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MyBaseClass&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;override&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;myMethod&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Custom implementation&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;By using virtual classes, developers can create a hierarchy of classes where each class adds or modifies the behavior of the base class, enabling flexibility and customization.&lt;/p&gt;
&lt;h2 id="abstract-classes"&gt;
&lt;a href="#abstract-classes" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Abstract Classes
&lt;/h2&gt;
&lt;p&gt;An &lt;code&gt;abstract&lt;/code&gt; class in Apex, on the other hand, is a class that cannot be instantiated directly. It serves as a blueprint or contract for derived classes, defining common methods and properties that subclasses must implement. Abstract classes allow developers to establish a common interface while leaving the specifics of implementation to the derived classes.&lt;/p&gt;
&lt;p&gt;To declare an abstract class in Apex, you use the &lt;code&gt;abstract&lt;/code&gt; keyword before the class keyword, like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;abstract&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyAbstractClass&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Class implementation goes here&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Abstract classes can contain &lt;code&gt;abstract&lt;/code&gt; methods, &lt;strong&gt;which are declared without an implementation.&lt;/strong&gt; These methods act as placeholders that must be implemented by any non-abstract subclass. For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;abstract&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyAbstractClass&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;abstract&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;myMethod&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Any class that extends an &lt;code&gt;abstract&lt;/code&gt; class must provide an implementation for all &lt;code&gt;abstract&lt;/code&gt; methods defined in the &lt;code&gt;abstract&lt;/code&gt; class. Failure to do so will result in a compilation error.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;abstract&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyAbstractClass&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;abstract&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;myMethod&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyConcreteClass&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;extends&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;MyAbstractClass&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;myMethod&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// Implementation of abstract method&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Abstract classes are useful when you want to define a common set of methods and properties for a group of related classes, ensuring consistent behavior across different implementations.&lt;/p&gt;
&lt;h2 id="choosing-between-them"&gt;
&lt;a href="#choosing-between-them" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Choosing between them
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Use a virtual class when a useful default implementation exists and subclasses may override selected virtual methods.&lt;/li&gt;
&lt;li&gt;Use an abstract class when the base must not be instantiated and subclasses must supply one or more abstract operations.&lt;/li&gt;
&lt;li&gt;Use an interface when unrelated implementations need the same contract without shared implementation.&lt;/li&gt;
&lt;li&gt;Prefer composition when inheritance would expose internals or create a fragile hierarchy.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Keep visibility explicit (&lt;code&gt;public&lt;/code&gt; or &lt;code&gt;global&lt;/code&gt; only when required), add &lt;code&gt;override&lt;/code&gt; to implementations, and write tests against both the base behavior and each specialization. &lt;code&gt;global&lt;/code&gt; expands the package-level compatibility commitment, so don&amp;rsquo;t use it as a default. Reviewed on 2026-08-01 against the &lt;a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_classes_defining.htm"&gt;Apex class definition reference&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Retrieve Flow Metadata from a Salesforce Scratch Org</title><link>https://www.yopa.page/blog/2023-05-11-retrieve-the-flow-metadata-from-the-scratch-org.html</link><pubDate>Thu, 11 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-11-retrieve-the-flow-metadata-from-the-scratch-org.html</guid><description>
&lt;p&gt;Retrieve only deliberate Flow changes and review the resulting source before committing it. The supported command is &lt;code&gt;sf project retrieve start&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;
&lt;a href="#prerequisites" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Prerequisites
&lt;/h2&gt;
&lt;p&gt;Before we dive into the solution, make sure you have the following set up:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The current &lt;code&gt;sf&lt;/code&gt; CLI installed&lt;/li&gt;
&lt;li&gt;A Salesforce DX project connected to your Dev Hub org&lt;/li&gt;
&lt;li&gt;A Scratch Org with the Flows you want to retrieve&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="retrieving-flow-metadata"&gt;
&lt;a href="#retrieving-flow-metadata" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Retrieving Flow Metadata
&lt;/h2&gt;
&lt;p&gt;To retrieve the metadata for Flows from your Scratch Org, open a terminal or command prompt and navigate to the root directory of your Salesforce DX project. Then, execute the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf project retrieve start --metadata &lt;span class="s2"&gt;&amp;#34;Flow:&amp;lt;flowApiName&amp;gt;&amp;#34;&lt;/span&gt; --target-org my-scratch
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Replace &lt;code&gt;&amp;lt;flowApiName&amp;gt;&lt;/code&gt; with the API name of the specific Flow you want to retrieve. If you want to retrieve multiple Flows, you can specify multiple Flow API names, separated by a comma.&lt;/p&gt;
&lt;p&gt;For example, let&amp;rsquo;s say you have a Flow with an API name of &lt;code&gt;&amp;quot;MyFlow&amp;quot;&lt;/code&gt;. To retrieve its metadata, you would run the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf project retrieve start --metadata &lt;span class="s2"&gt;&amp;#34;Flow:MyFlow&amp;#34;&lt;/span&gt; --target-org my-scratch
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The CLI converts retrieved metadata into project source format under the matching package directory, commonly &lt;code&gt;force-app&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="example"&gt;
&lt;a href="#example" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Example
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s consider a practical example where we have a Scratch Org containing two Flows: &lt;code&gt;&amp;quot;AccountFlow&amp;quot;&lt;/code&gt; and &lt;code&gt;&amp;quot;OpportunityFlow&amp;quot;&lt;/code&gt;. To retrieve the metadata for both Flows, we would execute the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf project retrieve start &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --metadata &lt;span class="s2"&gt;&amp;#34;Flow:AccountFlow&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --metadata &lt;span class="s2"&gt;&amp;#34;Flow:OpportunityFlow&amp;#34;&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --target-org my-scratch
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Flow metadata can include version and activation details. Inspect &lt;code&gt;git diff&lt;/code&gt;, confirm that unrelated local work wasn&amp;rsquo;t overwritten, and test the retrieved Flow in a disposable org. Source tracking helps identify changes, but Git remains the source of truth. Reviewed against the &lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-reference/guide/cli_reference.html"&gt;Salesforce CLI reference&lt;/a&gt; on 2026-08-01.&lt;/p&gt;</description></item><item><title>Log Out (Unlink) the Scratch Org from the Project in Salesforce using SFDX</title><link>https://www.yopa.page/blog/2023-05-10-log-out-unlink-the-scratch-org-from-the-project-in-salesforce-using-sfdx.html</link><pubDate>Wed, 10 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-10-log-out-unlink-the-scratch-org-from-the-project-in-salesforce-using-sfdx.html</guid><description>
&lt;p&gt;Logging out and deleting a scratch org are different operations. Logging out removes local authorization. Deleting a scratch org disposes of the remote org through its Dev Hub. Choose the operation that matches your intent.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;
&lt;a href="#prerequisites" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Prerequisites
&lt;/h2&gt;
&lt;p&gt;Before proceeding, make sure that you have the following:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Salesforce CLI installed on your local machine&lt;/li&gt;
&lt;li&gt;A Salesforce DX project connected to your Dev Hub org&lt;/li&gt;
&lt;li&gt;At least one Scratch Org created and authorized for the project&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="remove-local-authorization"&gt;
&lt;a href="#remove-local-authorization" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Remove local authorization
&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sf org logout --target-org my-scratch
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Then verify the local authorization list:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;sf org list auth
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;This does not delete the scratch org. If the project still targets that alias, select another with &lt;code&gt;sf config set target-org=another-org&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;To dispose of the org, first verify its ID with &lt;code&gt;sf org display --target-org my-scratch&lt;/code&gt;, then run &lt;code&gt;sf org delete scratch --target-org my-scratch&lt;/code&gt;. Keep the confirmation prompt for interactive work. Reviewed against the &lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-reference/guide/cli_reference_org.html"&gt;Salesforce CLI org reference&lt;/a&gt; on 2026-08-01.&lt;/p&gt;</description></item><item><title>Inspect Salesforce Orgs Without Exposing Secrets</title><link>https://www.yopa.page/blog/2026-08-01-inspect-salesforce-orgs-without-exposing-secrets.html</link><pubDate>Tue, 09 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-inspect-salesforce-orgs-without-exposing-secrets.html</guid><description>
&lt;p&gt;Verified on 2026-08-01. The old article used unsupported &lt;code&gt;sfdx&lt;/code&gt; syntax and printed an access-token-shaped value.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org display --target-org my-scratch
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org list --json &amp;gt;orgs.json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Prefer the human-readable display when sharing output, and filter JSON to the fields you need before attaching it to a ticket. Access tokens, auth URLs, client secrets, and private keys are secrets even when emitted by a developer tool. If a token has entered Git or a public log, remove the value from the visible document and revoke the corresponding session; rewriting a file alone does not invalidate it.&lt;/p&gt;
&lt;p&gt;Migration is a command rename plus a data-handling change: use &lt;code&gt;sf org display --target-org&lt;/code&gt;, stop requesting verbose auth fields, sanitize retained logs, and verify the alias, org ID, instance URL, status, and scratch-org expiration.&lt;/p&gt;
&lt;p&gt;Reference: &lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-reference/guide/cli_reference_org.html"&gt;Salesforce CLI org commands&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="decide-what-the-reader-needs"&gt;
&lt;a href="#decide-what-the-reader-needs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Decide what the reader needs
&lt;/h2&gt;
&lt;p&gt;Most diagnostics need an alias, org ID, instance URL, connection status, edition, or scratch-org expiration. None requires a bearer token. Separate identity/status inspection from the exceptional act of revealing authentication material.&lt;/p&gt;
&lt;h2 id="safe-inspection-workflow"&gt;
&lt;a href="#safe-inspection-workflow" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Safe inspection workflow
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org list --json &amp;gt;org-list.json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;jq &lt;span class="s1"&gt;&amp;#39;.result.scratchOrgs[] | {alias, orgId, instanceUrl, status, expirationDate}&amp;#39;&lt;/span&gt; org-list.json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org display --target-org my-scratch
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Write diagnostics to a permission-controlled temporary location and delete them when the incident ends. Before sharing, inspect the original JSON because plugin versions can add fields. Avoid &lt;code&gt;--verbose&lt;/code&gt; unless the exact additional field is understood. For CI, emit a small allowlisted object rather than applying a blacklist after the fact.&lt;/p&gt;
&lt;p&gt;If access-token use is genuinely required, call the purpose-built command only inside a controlled session. Never paste its output into chat, issues, screenshots, or build artifacts. A token already committed to Git must be revoked even after history cleanup; coordinate repository history rewriting because it affects collaborators.&lt;/p&gt;
&lt;h2 id="alternatives-and-tradeoffs"&gt;
&lt;a href="#alternatives-and-tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Alternatives and tradeoffs
&lt;/h2&gt;
&lt;p&gt;Human-readable output is safest for an operator but harder to automate. JSON is stable enough for scripts and exit-code checks but can carry more detail than expected. Salesforce Setup offers a visual view but is slower and difficult to capture reproducibly.&lt;/p&gt;
&lt;h2 id="migration-and-verification"&gt;
&lt;a href="#migration-and-verification" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration and verification
&lt;/h2&gt;
&lt;p&gt;Replace &lt;code&gt;sfdx force:org:display --verbose&lt;/code&gt;, remove token fields from examples and fixtures, rotate any plausible real token, and add allowlist-based redaction. Verify with a scratch org and sandbox, review the produced artifact manually, run a secret scanner, and confirm that aliases and expiration are sufficient for the operational task.&lt;/p&gt;</description></item><item><title>Understanding Class Diagrams and UML arrows</title><link>https://www.yopa.page/blog/2023-05-08-understanding-class-diagrams-and-uml-arrows.html</link><pubDate>Mon, 08 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-08-understanding-class-diagrams-and-uml-arrows.html</guid><description>
&lt;p&gt;Class diagrams (a type of UML diagram) are a key tool used in object-oriented programming to visualize the relationships between classes. These diagrams help developers to understand the structure of their code and how different classes interact with one another. One important aspect of class diagrams is the use of UML arrows to show relationships between classes.&lt;/p&gt;
&lt;h2 id="types-of-uml-arrows-in-class-diagrams"&gt;
&lt;a href="#types-of-uml-arrows-in-class-diagrams" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Types of UML Arrows in Class Diagrams
&lt;/h2&gt;
&lt;p&gt;There are several types of arrows that can be used in class diagrams to show different types of relationships between classes. Here are some of the most common ones:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Association Arrows
Association arrows are used to show that two classes are associated with one another in some way. For example, a Student class might be associated with a Course class if the student is enrolled in that course. Association arrows are typically drawn as a straight line with an arrowhead pointing from one class to another.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; +-----------+ +-----------+
| Course | --------&amp;gt;| Student |
+-----------+ +-----------+
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, the Course class is associated with the Student class, indicating that a student can enroll in one or more courses.&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Aggregation Arrows
Aggregation arrows are used to show that one class is a part of another class. For example, a &lt;code&gt;Car&lt;/code&gt; class might have an aggregation relationship with a &lt;code&gt;Wheel&lt;/code&gt; class, indicating that a car is made up of one or more wheels. Aggregation arrows are typically drawn as a straight line with an arrowhead pointing from the part class to the whole class, and a diamond shape on the whole class side.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; +-----------+ +-----------+
| Car | | Wheel |
+-----------+ +-----------+
◇ ◇
| |
+-------+ +-------+
| Wheel | | Wheel |
+-------+ +-------+
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, the Car class has an aggregation relationship with the Wheel class, indicating that a car is made up of one or more wheels.&lt;/p&gt;
&lt;ol start="3"&gt;
&lt;li&gt;Composition Arrows
Composition arrows are similar to aggregation arrows, but they indicate a stronger relationship between classes. In a composition relationship, the part class is completely contained within the whole class, and cannot exist independently. For example, a &lt;code&gt;House&lt;/code&gt; class might have a composition relationship with a &lt;code&gt;Room&lt;/code&gt; class, indicating that a room cannot exist outside of a house. Composition arrows are typically drawn with a filled diamond shape on the whole class side.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; +-----------+ +-----------+
| House | | Room |
+-----------+ +-----------+
◆ ◆
| |
+-------+ +-------+
| Room | | Room |
+-------+ +-------+
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, the &lt;code&gt;House&lt;/code&gt; class has a composition relationship with the &lt;code&gt;Room&lt;/code&gt; class, indicating that a room cannot exist outside of a house.&lt;/p&gt;
&lt;ol start="4"&gt;
&lt;li&gt;Inheritance Arrows
Inheritance arrows are used to show that one class inherits properties and methods from another class. For example, a &lt;code&gt;Dog&lt;/code&gt; class might inherit from an &lt;code&gt;Animal&lt;/code&gt; class, indicating that a dog is a type of animal. Inheritance arrows are typically drawn as a straight line with an arrowhead pointing from the subclass to the superclass, and a solid triangle on the superclass side.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; +-----------+ +-----------+
| Animal |◁--------| Dog |
+-----------+ +-----------+
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this example, the &lt;code&gt;Dog&lt;/code&gt; class inherits from the &lt;code&gt;Animal&lt;/code&gt; class, indicating that a dog is a type of animal.&lt;/p&gt;
&lt;h2 id="now-challenge-draw-dependency-injection"&gt;
&lt;a href="#now-challenge-draw-dependency-injection" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
NOW Challenge! Draw Dependency Injection!!
&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt; +----------------------+ +-----------------+
| Service Interfeace |&amp;lt;------------------ | Service Impl |
+----------------------+ implements +-----------------+
^ |
| depends on |
| |
+----------------------+ is provided |
| Component |&amp;lt;-----------------------------
+----------------------+
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;In this diagram, &lt;code&gt;Component&lt;/code&gt; depends on the &lt;code&gt;ServiceInterface&lt;/code&gt;. &lt;code&gt;ServiceImpl&lt;/code&gt; implements the &lt;code&gt;ServiceInterface&lt;/code&gt;, and an instance of &lt;code&gt;ServiceImpl&lt;/code&gt; is provided to &lt;code&gt;Component&lt;/code&gt;. This is an example of dependency injection, where &lt;code&gt;Component&lt;/code&gt; does not create an instance of &lt;code&gt;ServiceImpl&lt;/code&gt; itself, but rather receives it from an external source (e.g., a dependency injection framework or a constructor parameter).&lt;/p&gt;</description></item><item><title>Run Anonymous Apex Safely with sf CLI</title><link>https://www.yopa.page/blog/2026-08-01-run-anonymous-apex-safely-with-sf-cli.html</link><pubDate>Sun, 07 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-run-anonymous-apex-safely-with-sf-cli.html</guid><description>
&lt;p&gt;Verified on 2026-08-01. A &lt;code&gt;.cls&lt;/code&gt; file defines a class; it is not an anonymous script. The retired TypeScript wrapper also built a shell command from paths, creating an injection risk.&lt;/p&gt;
&lt;p&gt;Put intentional executable statements in a reviewed &lt;code&gt;.apex&lt;/code&gt; file and run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf apex run --file scripts/apex/seed-data.apex --target-org scratch --json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;For a Node wrapper, use &lt;code&gt;spawn&lt;/code&gt;/&lt;code&gt;execFile&lt;/code&gt; with an argument array, accept only &lt;code&gt;.apex&lt;/code&gt; files beneath an allowlisted directory, and parse JSON plus the process exit code. Never run every file discovered in a directory. Use Apex tests or a deployable class when code belongs in the application; anonymous Apex is best for controlled administration and repeatable setup scripts.&lt;/p&gt;
&lt;p&gt;Migration: rename only genuine scripts to &lt;code&gt;.apex&lt;/code&gt;, move class definitions to &lt;code&gt;force-app&lt;/code&gt;, review DML/callouts, test in a disposable org, then record the target alias explicitly.&lt;/p&gt;
&lt;p&gt;Reference: &lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-reference/guide/cli_reference_apex.html"&gt;Apex commands&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="current-mental-model"&gt;
&lt;a href="#current-mental-model" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Current mental model
&lt;/h2&gt;
&lt;p&gt;Anonymous Apex is an administrative script evaluated in one org context. A &lt;code&gt;.cls&lt;/code&gt; file is deployable source with metadata and lifecycle. Running every class file as a script confuses those boundaries and can execute DML against the wrong org.&lt;/p&gt;
&lt;h2 id="build-a-controlled-runner"&gt;
&lt;a href="#build-a-controlled-runner" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Build a controlled runner
&lt;/h2&gt;
&lt;p&gt;Keep scripts under &lt;code&gt;scripts/apex&lt;/code&gt;, review them like migrations, and make target selection explicit:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org display --target-org scratch
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf apex run --file scripts/apex/seed-data.apex &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --target-org scratch --json &amp;gt;apex-result.json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;jq &lt;span class="s1"&gt;&amp;#39;{status, success: .result.success, logs: .result.logs}&amp;#39;&lt;/span&gt; apex-result.json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The script itself should be idempotent where possible: query by a stable key, insert only missing records, and fail on unexpected duplicates. Avoid printing customer data, credentials, or full HTTP responses. Use Named Credentials for callouts and a disposable org for the first execution.&lt;/p&gt;
&lt;p&gt;A programmatic wrapper must resolve each candidate beneath the allowed directory, reject symlinks or extensions outside policy, and invoke the executable with an argument array. Limit concurrency, stop on the first failure unless independence is proven, and return a nonzero process exit code.&lt;/p&gt;
&lt;h2 id="alternatives-and-tradeoffs"&gt;
&lt;a href="#alternatives-and-tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Alternatives and tradeoffs
&lt;/h2&gt;
&lt;p&gt;Anonymous Apex is fast for reviewed one-off work but has weak deployment history. A deployable Apex class plus tests is better for reusable application behavior. Data plans or &lt;code&gt;sf data&lt;/code&gt; commands are clearer for seed data, while Metadata API is for configuration rather than records.&lt;/p&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;p&gt;Classify every old &lt;code&gt;.cls&lt;/code&gt;: deploy classes, convert only true statement scripts to &lt;code&gt;.apex&lt;/code&gt;, delete generated copies, and record ownership. Review DML, limits, sharing mode, callouts, and rollback. Test twice to prove idempotency, inspect the JSON success value, confirm expected records, and ensure no script output contains secrets.&lt;/p&gt;</description></item><item><title>Partially Applied Types with Type Currying TypeScript</title><link>https://www.yopa.page/blog/2023-05-06-partially-applied-types-with-type-currying-typescript.html</link><pubDate>Sat, 06 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-06-partially-applied-types-with-type-currying-typescript.html</guid><description>
&lt;p&gt;Type Currying is a technique used in functional programming that allows us to transform a function with multiple arguments into a series of functions that take a single argument. This makes the functions more flexible and easier to compose.&lt;/p&gt;
&lt;p&gt;Partially Applied Types is another powerful technique that can be used in conjunction with Type Currying to further enhance the flexibility and composability of functions. In this article, we will explore these concepts and their applications in functional programming.&lt;/p&gt;
&lt;h2 id="type-currying"&gt;
&lt;a href="#type-currying" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Type Currying
&lt;/h2&gt;
&lt;p&gt;Type Currying is named after Haskell Curry, a mathematician who introduced the concept of currying in the 20th century. The idea behind Type Currying is simple: transform a function that takes multiple arguments into a series of functions that each take a single argument.&lt;/p&gt;
&lt;p&gt;For example, let&amp;rsquo;s say we have a function add that takes two integers and returns their sum:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;We can transform this function into a series of functions that each take a single argument:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This new function add takes a single argument &lt;code&gt;x&lt;/code&gt; and returns another function that takes a single argument &lt;code&gt;y&lt;/code&gt; and returns their &lt;code&gt;sum&lt;/code&gt;. We can use this function like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;add5&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// returns a new function that adds 5 to its argument
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;add5&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// returns 8
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This technique can be used to create new functions from existing ones by partially applying some of the arguments.&lt;/p&gt;
&lt;h2 id="partially-applied-types"&gt;
&lt;a href="#partially-applied-types" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Partially Applied Types
&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Partially Applied Types&lt;/code&gt; is a technique that extends &lt;code&gt;Type Currying&lt;/code&gt; to support partially applying type parameters. In other words, it allows us to create new types by partially applying some of the type parameters of an existing type.&lt;/p&gt;
&lt;p&gt;For example, let&amp;rsquo;s say we have a generic type &lt;code&gt;Result&amp;lt;T, E&amp;gt;&lt;/code&gt; that represents the result of an operation that can either succeed with a value of type &lt;code&gt;T&lt;/code&gt; or fail with an error of type &lt;code&gt;E&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;E&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;E&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Success&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;success&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;value&lt;/span&gt;: &lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Failure&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;E&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;failure&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;error&lt;/span&gt;: &lt;span class="kt"&gt;E&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;We can use this type to represent the result of any operation that can fail, such as a network request that can either return data or an error.&lt;/p&gt;
&lt;p&gt;Now, let&amp;rsquo;s say we have a specific use case where we only care about the successful case and we don&amp;rsquo;t need to handle errors. We can create a new type &lt;code&gt;SuccessResult&amp;lt;T&amp;gt;&lt;/code&gt; by partially applying the &lt;code&gt;Result&lt;/code&gt; type:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;SuccessResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;never&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Usage:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;: &lt;span class="kt"&gt;SuccessResult&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;success&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;: &lt;span class="kt"&gt;42&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this new type, we have partially applied the second type parameter &lt;code&gt;E&lt;/code&gt; with the &lt;code&gt;never&lt;/code&gt; type, which means we don&amp;rsquo;t care about the error case. This new type can be used in place of the original &lt;code&gt;Result&lt;/code&gt; type wherever we only care about the successful case.&lt;/p&gt;</description></item><item><title>Manage environment variables in TypeScript</title><link>https://www.yopa.page/blog/2023-05-05-manage-environment-variables-in-typescript.html</link><pubDate>Fri, 05 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-05-manage-environment-variables-in-typescript.html</guid><description>
&lt;h2 id="current-nodejs-options-and-secret-boundaries"&gt;
&lt;a href="#current-nodejs-options-and-secret-boundaries" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Current Node.js options and secret boundaries
&lt;/h2&gt;
&lt;p&gt;Current Node.js can load an environment file with &lt;code&gt;node --env-file=.env&lt;/code&gt; where supported; &lt;code&gt;dotenv&lt;/code&gt; remains a valid cross-version option. In either case, validate required values at startup, never commit secrets, and use the deployment platform secret store for credentials.&lt;/p&gt;
&lt;p&gt;In a typical web application, you might have sensitive information like database credentials, API keys, and other configuration data that you don&amp;rsquo;t want to hardcode into your codebase. Instead, you can use environment variables to keep this data separate and secure. In this article, we&amp;rsquo;ll discuss how to use the &lt;code&gt;dotenv&lt;/code&gt; library to manage environment variables in TypeScript.&lt;/p&gt;
&lt;h2 id="what-is-dotenv"&gt;
&lt;a href="#what-is-dotenv" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What is dotenv?
&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;dotenv&lt;/code&gt; is a popular library that loads environment variables from a &lt;code&gt;.env&lt;/code&gt; file into &lt;code&gt;process.env&lt;/code&gt;, making it easy to access them in your code. With &lt;code&gt;dotenv&lt;/code&gt;, you can keep your sensitive information separate from your codebase, making it easier to manage and secure.&lt;/p&gt;
&lt;h2 id="installation"&gt;
&lt;a href="#installation" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Installation
&lt;/h2&gt;
&lt;p&gt;To get started with &lt;code&gt;dotenv&lt;/code&gt;, you need to install it as a dependency in your project.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm install dotenv
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="usage"&gt;
&lt;a href="#usage" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Usage
&lt;/h2&gt;
&lt;p&gt;Once you have installed &lt;code&gt;dotenv&lt;/code&gt;, you need to create a &lt;code&gt;.env&lt;/code&gt; file in the &lt;strong&gt;root&lt;/strong&gt; of your project. This file should contain your environment variables in the &lt;code&gt;KEY=VALUE&lt;/code&gt; format, with each variable on a new line.
For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-sh" data-lang="sh"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mongodb://localhost:27017/mydb
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;abc123
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In your TypeScript code, you can now access these environment variables using &lt;code&gt;process.env&lt;/code&gt;.
For example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;dotenv&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;dotenv&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dbUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;DATABASE_URL&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;apiKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Note that you need to call &lt;code&gt;dotenv.config()&lt;/code&gt; to load the variables from the &lt;code&gt;.env&lt;/code&gt; file into &lt;code&gt;process.env&lt;/code&gt;. You can call this function at the beginning of your application or whenever you need to load new variables.&lt;/p&gt;</description></item><item><title>What is a Record with Self-Referencing in Salesforce and Why Do We Need It?</title><link>https://www.yopa.page/blog/2023-05-04-what-is-a-record-with-self-referencing-in-salesforce-why-and-when-we-need-it.html</link><pubDate>Thu, 04 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-04-what-is-a-record-with-self-referencing-in-salesforce-why-and-when-we-need-it.html</guid><description>
&lt;p&gt;A self-referencing relationship points from a record to another record of the same object. It is useful when the relationship is genuinely part of the domain, such as a parent account, previous product version, or reporting hierarchy.&lt;/p&gt;
&lt;h2 id="when-a-self-reference-fits"&gt;
&lt;a href="#when-a-self-reference-fits" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
When a self-reference fits
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;One of the primary use cases for self-referencing records in Salesforce is for managing hierarchical data. For example, an organization might use self-referencing records to manage a tree structure of departments or teams. Each department or team can have a parent department or team, and this relationship can be easily modeled using self-referencing fields.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Another use case for self-referencing records is for managing parent-child relationships. For example, a company might use self-referencing records to manage a list of accounts, where each account can have a parent account. This relationship can be modeled using a self-referencing field on the Account object.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;A same-object lookup can model a predecessor or preferred alternative. If records need many-to-many relationships, attributes on the relationship, or multiple alternatives, use a junction object instead of adding more lookup fields.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="example-of-record-with-self-referencing-in-salesforce"&gt;
&lt;a href="#example-of-record-with-self-referencing-in-salesforce" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Example of Record with Self-Referencing in Salesforce
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s say that you are working for a company that sells software products. You have been tasked with setting up a new product catalog in Salesforce, and you want to be able to manage relationships between products. You decide to use self-referencing records to model these relationships.&lt;/p&gt;
&lt;p&gt;You create a new custom object called &amp;ldquo;Product,&amp;rdquo; and you add a new self-referencing field called &amp;ldquo;Related Product.&amp;rdquo; This field allows you to associate a product with another product as a substitute or alternative.&lt;/p&gt;
&lt;p&gt;You then create a new record for your flagship product, &amp;ldquo;Awesome Software 2.0.&amp;rdquo; In the &amp;ldquo;Related Product&amp;rdquo; field, you associate it with your previous version, &amp;ldquo;Awesome Software 1.0.&amp;rdquo; This allows your sales team to easily suggest the newer version as an alternative when selling to existing customers.&lt;/p&gt;
&lt;p&gt;You also create a new record for a complementary product, &amp;ldquo;Awesome Add-On.&amp;rdquo; In the &amp;ldquo;Related Product&amp;rdquo; field, you associate it with &amp;ldquo;Awesome Software 2.0.&amp;rdquo; This allows your sales team to easily suggest the add-on when selling the main product.&lt;/p&gt;
&lt;h2 id="how-to-create-a-record-with-self-referencing-in-salesforce"&gt;
&lt;a href="#how-to-create-a-record-with-self-referencing-in-salesforce" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
How to Create a Record with Self-Referencing in Salesforce
&lt;/h2&gt;
&lt;p&gt;Create the relationship only after deciding its cardinality, delete behavior, access model, and reporting needs:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Navigate to Setup by clicking on the gear icon in the top right corner of the screen.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;In the left-hand menu, select &amp;ldquo;Object Manager.&amp;rdquo;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Select the object for which you want to create a self-referencing field.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click on the &amp;ldquo;Fields &amp;amp; Relationships&amp;rdquo; tab.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click the &amp;ldquo;New&amp;rdquo; button to create a new field.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Select the &amp;ldquo;Lookup Relationship&amp;rdquo; field type.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Choose the same object as the related object.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Give your field a name, such as &amp;ldquo;Related Product&amp;rdquo; in our example above.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click &amp;ldquo;Next.&amp;rdquo;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Choose the page layouts where you want the field to appear.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Click &amp;ldquo;Save.&amp;rdquo;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Test more than the happy path. Decide whether cycles such as A → B → A are valid, prevent them with Flow or Apex when they are not, and verify sharing access to both records. Lookup relationships do not automatically provide every roll-up behavior a master-detail relationship does. Also check reporting depth and deletion behavior before importing a hierarchy.&lt;/p&gt;
&lt;p&gt;Reviewed on 2026-08-01 against Salesforce&amp;rsquo;s &lt;a href="https://help.salesforce.com/s/articleView?id=platform.overview_of_custom_object_relationships.htm&amp;amp;type=5"&gt;Object Relationships overview&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Apex Nested Enums and Name Shadowing</title><link>https://www.yopa.page/blog/2026-08-01-apex-nested-enums-and-name-shadowing.html</link><pubDate>Wed, 03 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-apex-nested-enums-and-name-shadowing.html</guid><description>
&lt;p&gt;Verified on 2026-08-01. The retired article showed the same expression as both failing and fixed, so it could not support its diagnosis.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-apex" data-lang="apex"&gt;public class OrderRules {
public enum Stage { Draft, Approved }
}
@IsTest private class OrderRulesTest {
@IsTest static void resolvesNestedEnum() {
OrderRules.Stage stage = OrderRules.Stage.Approved;
System.assertEquals(OrderRules.Stage.Approved, stage);
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Use type-like names for classes and lower camel case for variables; avoid identifiers that differ only by case because Apex is case-insensitive. If an old test fails, reduce it to a compiling example, rename the local variable, deploy the class before the test, and verify namespace/package visibility. The tradeoff is only naming consistency; there is no runtime technique required.&lt;/p&gt;
&lt;p&gt;Reference: &lt;a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_language_reference.htm"&gt;Apex Developer Guide&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="current-mental-model"&gt;
&lt;a href="#current-mental-model" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Current mental model
&lt;/h2&gt;
&lt;p&gt;A nested enum is a type owned by its enclosing class, so the stable reference is &lt;code&gt;Outer.EnumName.Value&lt;/code&gt;. Apex identifiers are case-insensitive. A local variable that differs from a class name only by capitalization is therefore poor evidence for a compiler diagnosis and difficult for readers to distinguish.&lt;/p&gt;
&lt;h2 id="diagnose-the-actual-failure"&gt;
&lt;a href="#diagnose-the-actual-failure" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Diagnose the actual failure
&lt;/h2&gt;
&lt;p&gt;Compile the production class first, then a minimal test containing no unrelated setup. Check the exact compiler line, API version, access modifier, namespace, and package dependency. A test in another namespace may have a visibility problem; a test compiled before its dependency may have a deployment-order problem. Renaming a local variable is good hygiene, but it should not be presented as a universal fix without a reproducer.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-apex" data-lang="apex"&gt;public class OrderRules {
public enum Stage { Draft, Approved }
public static Boolean canShip(Stage value) { return value == Stage.Approved; }
}
@IsTest private class OrderRulesTest {
@IsTest static void approvedCanShip() {
OrderRules.Stage currentStage = OrderRules.Stage.Approved;
System.assert(OrderRules.canShip(currentStage));
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="alternatives-and-tradeoffs"&gt;
&lt;a href="#alternatives-and-tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Alternatives and tradeoffs
&lt;/h2&gt;
&lt;p&gt;Nested enums keep a small domain vocabulary close to its owner. A top-level enum is easier to share across unrelated classes but enlarges the public surface. Constants may integrate with strings but lose exhaustive type checking and permit invalid values.&lt;/p&gt;
&lt;h2 id="migration-and-verification"&gt;
&lt;a href="#migration-and-verification" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration and verification
&lt;/h2&gt;
&lt;p&gt;Rename ambiguous variables, qualify nested types, remove stringly typed comparisons, and add one test per meaningful enum branch. Deploy the class and test together to a disposable org and run &lt;code&gt;sf apex run test --tests OrderRulesTest --target-org scratch --wait 10&lt;/code&gt;. Confirm that package consumers can access only the visibility you intend.&lt;/p&gt;</description></item><item><title>Shell vs Bash vs Zsh vs Terminal</title><link>https://www.yopa.page/blog/2023-05-02-shell-vs-bash-vs-zsh-vs-terminal.html</link><pubDate>Tue, 02 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-05-02-shell-vs-bash-vs-zsh-vs-terminal.html</guid><description>
&lt;p&gt;The terms shell, Bash, Zsh, and terminal describe related but different parts of a command-line environment. Distinguishing the interface from the shell implementation makes configuration and troubleshooting easier.&lt;/p&gt;
&lt;h2 id="what-is-a-shell"&gt;
&lt;a href="#what-is-a-shell" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What is a Shell?
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s start with the basics: what is a &lt;code&gt;shell&lt;/code&gt;? &lt;strong&gt;A shell is a program that provides a command-line interface for interacting with the operating system.&lt;/strong&gt; Think of it as the bridge between you and the computer. It&amp;rsquo;s the tool you use to type in commands and execute them.&lt;/p&gt;
&lt;h2 id="bash-vs-zsh-whats-the-difference"&gt;
&lt;a href="#bash-vs-zsh-whats-the-difference" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Bash vs. Zsh: What&amp;rsquo;s the Difference?
&lt;/h2&gt;
&lt;p&gt;Now, let&amp;rsquo;s talk about &lt;code&gt;Bash&lt;/code&gt; and &lt;code&gt;Zsh&lt;/code&gt;. &lt;strong&gt;These are both Unix shells&lt;/strong&gt;, but they have some differences. Bash (short for Bourne-Again SHell) is the default shell on many Unix-based operating systems. It&amp;rsquo;s been around since the 1980s and is widely used. Zsh (short for Z Shell) is another Unix shell that is gaining popularity in recent years. It has some features that Bash doesn&amp;rsquo;t, such as improved tab completion and syntax highlighting.&lt;/p&gt;
&lt;h2 id="terminal-its-not-what-you-think"&gt;
&lt;a href="#terminal-its-not-what-you-think" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Terminal: It&amp;rsquo;s Not What You Think
&lt;/h2&gt;
&lt;p&gt;Finally, let&amp;rsquo;s talk about the &lt;code&gt;terminal&lt;/code&gt;. &lt;strong&gt;This is the program you use to interact with the shell.&lt;/strong&gt; It provides a window or interface where you can type in commands and see the output. However, some people use the term &amp;ldquo;terminal&amp;rdquo; to refer to the shell itself. This can lead to confusion, as the terminal and the shell are not the same thing.&lt;/p&gt;
&lt;h2 id="so-whats-the-verdict"&gt;
&lt;a href="#so-whats-the-verdict" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
So, What&amp;rsquo;s the Verdict?
&lt;/h2&gt;
&lt;p&gt;To sum up, a shell is a program that provides a command-line interface for interacting with the operating system. Bash and Zsh are both Unix shells, with some differences in features. The terminal is the program you use to interact with the shell, not the shell itself.&lt;/p&gt;</description></item><item><title>Salesforce CLI Authentication Without Token Leaks</title><link>https://www.yopa.page/blog/2026-08-01-salesforce-cli-authentication-without-token-leaks.html</link><pubDate>Mon, 01 May 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-salesforce-cli-authentication-without-token-leaks.html</guid><description>
&lt;p&gt;Verified on 2026-08-01. Scraping an access token from verbose org output is unsafe: a bearer token grants access to whoever obtains it.&lt;/p&gt;
&lt;p&gt;For interactive work, authenticate and use the alias directly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org login web --alias dev --instance-url https://login.salesforce.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org display --target-org dev
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Most CLI commands use the stored authorization without exposing a token. Only for a controlled diagnostic that truly needs a token, use &lt;code&gt;sf org auth show-access-token --target-org dev&lt;/code&gt;, keep it out of terminal recording and CI logs, and revoke or rotate it after exposure. Automation should use an approved noninteractive OAuth flow and a secret manager; do not commit auth URLs, private keys, or tokens.&lt;/p&gt;
&lt;p&gt;Migration means deleting token-scraping pipelines, passing &lt;code&gt;--target-org&lt;/code&gt; instead, checking log history for leakage, and revoking affected sessions. Web login is convenient for people; JWT or workload-oriented federation is more repeatable for CI but requires key and policy management.&lt;/p&gt;
&lt;p&gt;References: &lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-reference/guide/cli_reference_org.html"&gt;org commands&lt;/a&gt;, &lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-plugin/guide/migrate-sfdx-sf.html"&gt;CLI migration&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="threat-model-and-current-practice"&gt;
&lt;a href="#threat-model-and-current-practice" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Threat model and current practice
&lt;/h2&gt;
&lt;p&gt;An access token is a short-lived credential, not harmless diagnostic text. Shell history, CI annotations, screen recordings, support bundles, and copied JSON can turn a momentary display into durable exposure. The safest workflow is to let the CLI use its authorization store and pass an alias instead of moving tokens between processes.&lt;/p&gt;
&lt;h2 id="safe-step-by-step-workflow"&gt;
&lt;a href="#safe-step-by-step-workflow" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Safe step-by-step workflow
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org login web --alias dev --instance-url https://login.salesforce.com
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org display --target-org dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf data query --query &lt;span class="s2"&gt;&amp;#34;SELECT Id FROM Organization&amp;#34;&lt;/span&gt; --target-org dev --json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The ordinary command proves the session works without revealing the bearer token. Use a sandbox or developer org for troubleshooting. Redact org usernames and IDs when they are sensitive to your organization. If a raw token is unavoidable for a controlled interoperability test, disable command tracing, avoid pipes and temporary files, limit scope and lifetime where the authorization method allows it, and revoke the session afterward.&lt;/p&gt;
&lt;h2 id="automation-alternatives"&gt;
&lt;a href="#automation-alternatives" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Automation alternatives
&lt;/h2&gt;
&lt;p&gt;Interactive web login is easiest for a person but unsuitable for headless CI. JWT bearer flow is repeatable but requires protecting and rotating a private key. An approved workload identity or secret-manager-backed OAuth setup can reduce long-lived secrets, though availability depends on your Salesforce and CI architecture. SFDX auth URLs are credentials too; never commit them.&lt;/p&gt;
&lt;h2 id="incident-and-migration-checklist"&gt;
&lt;a href="#incident-and-migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Incident and migration checklist
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Remove token extraction from scripts and pass &lt;code&gt;--target-org&lt;/code&gt; to downstream CLI commands.&lt;/li&gt;
&lt;li&gt;Search Git history, CI logs, tickets, and artifacts for exposed values without echoing them again.&lt;/li&gt;
&lt;li&gt;Revoke affected sessions and rotate related credentials.&lt;/li&gt;
&lt;li&gt;Review the connected or external client application&amp;rsquo;s scopes and policies.&lt;/li&gt;
&lt;li&gt;Add log masking and a secret scanner, then test with synthetic values.&lt;/li&gt;
&lt;li&gt;Verify the replacement flow using a least-privileged nonproduction identity.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Deleting a visible token from a document does not revoke it. Revocation is the containment step; redaction prevents further disclosure.&lt;/p&gt;</description></item><item><title>How to Switch Scratch Orgs for a VS Code Project Using CLI</title><link>https://www.yopa.page/blog/2023-04-30-how-to-switch-scratch-orgs-for-a-vs-code-project-using-cli.html</link><pubDate>Sun, 30 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-30-how-to-switch-scratch-orgs-for-a-vs-code-project-using-cli.html</guid><description>
&lt;p&gt;Salesforce CLI uses the &lt;code&gt;target-org&lt;/code&gt; configuration value when a command doesn&amp;rsquo;t receive an explicit target. Give each org a recognizable alias, and prefer an explicit &lt;code&gt;--target-org&lt;/code&gt; in automation.&lt;/p&gt;
&lt;h2 id="authorize-and-select-the-org"&gt;
&lt;a href="#authorize-and-select-the-org" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Authorize and select the org
&lt;/h2&gt;
&lt;p&gt;Authorize the org only if it isn&amp;rsquo;t already available locally:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org login web --instance-url https://test.salesforce.com --alias my-scratch
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Setting an alias during login does not guarantee that the org becomes this project&amp;rsquo;s default. Set that choice explicitly from the project directory:&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Update your project&amp;rsquo;s configuration file to use the new Scratch Org by running the following command:&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf config &lt;span class="nb"&gt;set&lt;/span&gt; target-org&lt;span class="o"&gt;=&lt;/span&gt;my-scratch
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org display --target-org my-scratch
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Verify that VS Code shows the same alias in its status bar. If it doesn&amp;rsquo;t, use the Salesforce extension&amp;rsquo;s org-selection command and check &lt;code&gt;sf config get target-org&lt;/code&gt;. In CI or before a destructive operation, pass &lt;code&gt;--target-org my-scratch&lt;/code&gt; rather than relying on ambient configuration.&lt;/p&gt;
&lt;p&gt;Reviewed against the &lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-reference/guide/cli_reference_org.html"&gt;Salesforce CLI org reference&lt;/a&gt; on 2026-08-01.&lt;/p&gt;</description></item><item><title>Better Validations with includes and Selector Function in TypeScript</title><link>https://www.yopa.page/blog/2023-04-29-better-validations-with-includes-and-selector-function-in-typescript.html</link><pubDate>Sat, 29 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-29-better-validations-with-includes-and-selector-function-in-typescript.html</guid><description>
&lt;p&gt;As a developer, ensuring that your code is robust and free from errors is crucial. One way to achieve this is through proper validation of data inputs. TypeScript provides many tools for type checking and validation, and in this article, we will explore how to use the &lt;code&gt;includes&lt;/code&gt; method and &lt;code&gt;selector&lt;/code&gt; function for better validations.&lt;/p&gt;
&lt;h2 id="the-includes-method"&gt;
&lt;a href="#the-includes-method" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The &lt;code&gt;includes&lt;/code&gt; Method
&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;includes&lt;/code&gt; method is a built-in JavaScript method that checks if an array or string includes a specific value. In TypeScript, we can use &lt;code&gt;includes&lt;/code&gt; to validate data inputs against a set of allowed values.&lt;/p&gt;
&lt;p&gt;For example, let&amp;rsquo;s say we have a function that takes in a string parameter and needs to validate that it is either &amp;ldquo;foo&amp;rdquo; or &amp;ldquo;bar&amp;rdquo;. We can use &lt;code&gt;includes&lt;/code&gt; to achieve this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;validateInput&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;allowedValues&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;bar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;allowedValues&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;includes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, we have an array &lt;code&gt;allowedValues&lt;/code&gt; that contains the allowed values. We then use &lt;code&gt;includes&lt;/code&gt; to check if the input parameter is included in the array. The function returns &lt;code&gt;true&lt;/code&gt; if the input is valid and &lt;code&gt;false&lt;/code&gt; if it is not.&lt;/p&gt;
&lt;h2 id="the-selector-function"&gt;
&lt;a href="#the-selector-function" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Selector Function
&lt;/h2&gt;
&lt;p&gt;The selector function is a TypeScript feature that allows us to define a function that selects a specific property from an object. This can be useful when validating complex data structures.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say we have an interface &lt;code&gt;Person&lt;/code&gt; that represents a person&amp;rsquo;s information:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Person&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;age&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;email&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now let&amp;rsquo;s say we have an array of &lt;code&gt;Person&lt;/code&gt; objects and we want to validate that all of the &lt;code&gt;email&lt;/code&gt; properties are valid email addresses. We can use a selector function to achieve this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;validateEmails&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;people&lt;/span&gt;: &lt;span class="kt"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;boolean&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;getEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;: &lt;span class="kt"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;emailRegex&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sr"&gt;/^[^\s@]+@[^\s@]+\.[^\s@]+$/&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;people&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;every&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;emailRegex&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;getEmail&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;person&lt;/span&gt;&lt;span class="p"&gt;)));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, we have a &lt;code&gt;getEmail&lt;/code&gt; function that takes in a &lt;code&gt;Person&lt;/code&gt; object and returns the &lt;code&gt;email&lt;/code&gt; property. We then use every to check that every &lt;code&gt;Person&lt;/code&gt; object in the array has a valid email address.&lt;/p&gt;</description></item><item><title>How to Fix This Schedulable Class Has Jobs Pending or In Progress Error</title><link>https://www.yopa.page/blog/2023-04-28-how-to-fix-this-schedulable-class-has-jobs-pending-or-in-progress-error.html</link><pubDate>Fri, 28 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-28-how-to-fix-this-schedulable-class-has-jobs-pending-or-in-progress-error.html</guid><description>
&lt;p&gt;Salesforce can block deployment of a schedulable Apex class while jobs for that class are queued or running. Treat the jobs as production work to understand, not as an obstacle to bypass automatically.&lt;/p&gt;
&lt;h2 id="diagnose-before-changing-deployment-settings"&gt;
&lt;a href="#diagnose-before-changing-deployment-settings" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Diagnose before changing deployment settings
&lt;/h2&gt;
&lt;p&gt;In Setup, review &lt;strong&gt;Scheduled Jobs&lt;/strong&gt; and &lt;strong&gt;Apex Jobs&lt;/strong&gt;. Confirm which class is running, who owns the schedule, when it runs next, and whether the deployment changes its behavior or state. In a scratch org, deleting and recreating a disposable schedule may be reasonable. In a shared or production org, coordinate a maintenance window and preserve the schedule expression and owner before aborting anything.&lt;/p&gt;
&lt;p&gt;If it is safe to stop a job, abort the specific job through Setup or a reviewed Apex administration procedure, deploy with the current CLI, then reschedule and verify it:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf project deploy start --source-dir force-app/main/default/classes &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --target-org staging --dry-run --test-level RunLocalTests
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The Deployment Settings option &lt;strong&gt;Allow deployments of components when corresponding Apex jobs are pending or in progress&lt;/strong&gt; is a governed exception, not the default fix. It permits class replacement while related work exists, so use it only after assessing compatibility and rollback. Record the previous setting and restore it after the controlled deployment if your policy requires that.&lt;/p&gt;
&lt;p&gt;Verification includes the deployment result, Apex tests, the recreated schedule, and the next successful job execution. Reviewed on 2026-08-01 against the &lt;a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/apex_scheduler.htm"&gt;Apex Scheduler documentation&lt;/a&gt; and current Salesforce CLI reference.&lt;/p&gt;</description></item><item><title>Level Up Your JavaScript Skills with Lodash</title><link>https://www.yopa.page/blog/2023-04-27-lodash-in-javascript.html</link><pubDate>Thu, 27 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-27-lodash-in-javascript.html</guid><description>
&lt;h2 id="lodash"&gt;
&lt;a href="#lodash" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Lodash
&lt;/h2&gt;
&lt;p&gt;Lodash is a powerful utility library that provides a ton of helpful functions for working with arrays, objects, strings, and more.&lt;/p&gt;
&lt;p&gt;You can import the entire library like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt; &lt;span class="nx"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;lodash&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;One of the main benefits of Lodash is that it can help you write cleaner, more concise code. Let&amp;rsquo;s take a look at a quick example.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Without Lodash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: 15
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// With Lodash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: 15
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, we&amp;rsquo;re calculating the sum of an array of numbers. Without Lodash, we need to use a &lt;code&gt;for&lt;/code&gt; loop to iterate over the array and add up each value. With Lodash, we can use the &lt;code&gt;sum&lt;/code&gt; function, which takes an array as its argument and returns the sum of all the values in the array. This saves us several lines of code and makes the logic much easier to read.&lt;/p&gt;
&lt;p&gt;But that&amp;rsquo;s just the tip of the iceberg when it comes to Lodash. Here are some other handy functions you can use:&lt;/p&gt;
&lt;h3 id="map"&gt;
&lt;a href="#map" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
map
&lt;/h3&gt;
&lt;p&gt;map creates a new array with the results of calling a provided function on every element in the original array.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;doubledNumbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;num&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;doubledNumbers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: [2, 4, 6, 8, 10]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Bob&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Charlie&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;nameLengths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nameLengths&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: [5, 3, 7]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="filter"&gt;
&lt;a href="#filter" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
filter
&lt;/h3&gt;
&lt;p&gt;filter creates a new array with all elements that pass the test implemented by the provided function.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;evenNumbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;num&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;evenNumbers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: [2, 4]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Bob&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Charlie&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;longNames&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;longNames&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: [&amp;#34;Charlie&amp;#34;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="find"&gt;
&lt;a href="#find" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
find
&lt;/h3&gt;
&lt;p&gt;find returns the first element in an array that satisfies a provided testing function.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numbers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstEvenNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;numbers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;num&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;num&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;firstEvenNumber&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: 2
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;names&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Bob&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Charlie&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;longName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;names&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;longName&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// Output: &amp;#34;Charlie&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h3 id="orderby"&gt;
&lt;a href="#orderby" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
orderBy
&lt;/h3&gt;
&lt;p&gt;orderBy sorts an array by one or more properties.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-javascript" data-lang="javascript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Bob&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Charlie&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;35&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sortedUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;_&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orderBy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;age&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;asc&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;desc&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sortedUsers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;/*
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;Output: [
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; { name: &amp;#34;Bob&amp;#34;, age: 25 },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; { name: &amp;#34;Alice&amp;#34;, age: 30 },
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt; { name: &amp;#34;Charlie&amp;#34;, age: 35 }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cm"&gt;*/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>Resolve a Conflict with Other Devs at Work</title><link>https://www.yopa.page/blog/2023-04-26-resolve-a-conflict-with-other-devs-at-work.html</link><pubDate>Wed, 26 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-26-resolve-a-conflict-with-other-devs-at-work.html</guid><description>
&lt;p&gt;So, you&amp;rsquo;ve found yourself in a bit of a pickle with another developer at work. Maybe they disagree with your coding style, or they think your approach to a project is all wrong. Whatever the reason, you&amp;rsquo;re not seeing eye-to-eye and it&amp;rsquo;s starting to affect your working relationship. Here is my two cents.&lt;/p&gt;
&lt;h2 id="step-1-take-a-deep-breath"&gt;
&lt;a href="#step-1-take-a-deep-breath" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 1: Take a Deep Breath
&lt;/h2&gt;
&lt;p&gt;First things first, take a deep breath and remember that you&amp;rsquo;re both on the same team. It&amp;rsquo;s not you against them, it&amp;rsquo;s both of you working towards the same goal. This conflict is just a bump in the road and it will pass. So, relax and try to approach the situation with an open mind.&lt;/p&gt;
&lt;h2 id="step-2-listen"&gt;
&lt;a href="#step-2-listen" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 2: Listen
&lt;/h2&gt;
&lt;p&gt;Now that you&amp;rsquo;re feeling a bit more zen, it&amp;rsquo;s time to hear them out. Really listen to what they have to say and try to understand where they&amp;rsquo;re coming from. Maybe they have some valid points that you haven&amp;rsquo;t considered. Or maybe they just need to vent a little. Either way, let them have their say and don&amp;rsquo;t interrupt.&lt;/p&gt;
&lt;h2 id="step-3-state-your-case"&gt;
&lt;a href="#step-3-state-your-case" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 3: State Your Case
&lt;/h2&gt;
&lt;p&gt;Once they&amp;rsquo;ve had their turn, it&amp;rsquo;s time for you to state your case. Explain why you&amp;rsquo;ve made the decisions you have and why you think they&amp;rsquo;re the right ones. Be prepared to provide evidence to back up your claims. This isn&amp;rsquo;t a courtroom, but it doesn&amp;rsquo;t hurt to have some facts on your side.&lt;/p&gt;
&lt;h2 id="step-4-compromise"&gt;
&lt;a href="#step-4-compromise" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 4: Compromise
&lt;/h2&gt;
&lt;p&gt;Now that you&amp;rsquo;ve both had your say, it&amp;rsquo;s time to find some common ground. Is there a way to combine your ideas? Can you meet in the middle somewhere? Maybe you can agree to try things their way for a little while and see how it goes. The important thing is to find a solution that works for both of you.&lt;/p&gt;
&lt;h2 id="step-5-fist-bump"&gt;
&lt;a href="#step-5-fist-bump" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step 5: Fist Bump
&lt;/h2&gt;
&lt;p&gt;Congratulations! You&amp;rsquo;ve successfully resolved a conflict with another dev. Now it&amp;rsquo;s time to seal the deal with a handshake (or a fist bump if that&amp;rsquo;s more your style). Remember, you&amp;rsquo;re both on the same team and you&amp;rsquo;re both working towards the same goal. So, put this conflict behind you and get back to coding like the superstars you are.&lt;/p&gt;
&lt;p&gt;I believe, avoiding conflicts is not always the best approach, but neither is facing them in a destructive way. It&amp;rsquo;s important to find a balance and approach conflicts with an open mind, a willingness to listen and understand, and a commitment to finding a mutually beneficial solution.&lt;/p&gt;
&lt;p&gt;Cheers! 🍺&lt;/p&gt;</description></item><item><title>Check Salesforce Object Availability with sf CLI</title><link>https://www.yopa.page/blog/2026-08-01-check-salesforce-object-availability-with-sf-cli.html</link><pubDate>Tue, 25 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-check-salesforce-object-availability-with-sf-cli.html</guid><description>
&lt;p&gt;Verified on 2026-08-01. The old schema-list flags did not provide a dependable existence test.&lt;/p&gt;
&lt;p&gt;Ask the target org to describe the exact API name:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; sf sobject describe --sobject MyObject__c --target-org &lt;span class="nb"&gt;test&lt;/span&gt; --json &amp;gt;describe.json&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;object is available&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;else&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;object is absent or inaccessible&amp;#34;&lt;/span&gt; &amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;A failed describe can also mean the authenticated user lacks access. That distinction is important: object metadata, licensing, installed packages, and permissions all affect visibility. For automation, inspect the JSON status and error code without printing credentials. For Apex runtime logic, use schema describe methods and enforce CRUD/FLS rather than shelling out.&lt;/p&gt;
&lt;p&gt;Migrate by replacing &lt;code&gt;sfdx force:schema:*&lt;/code&gt; calls with the current &lt;code&gt;sf sobject describe&lt;/code&gt; command and by testing both an existing and deliberately missing object in the intended org.&lt;/p&gt;
&lt;p&gt;Reference: &lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-reference/guide/cli_reference.html"&gt;Salesforce CLI command reference&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="current-mental-model"&gt;
&lt;a href="#current-mental-model" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Current mental model
&lt;/h2&gt;
&lt;p&gt;“Exists” can mean three things: the metadata type exists in the org, the authenticated principal can see it, and the object is usable for the intended operation. A describe request answers the first two together; a permission or license difference can therefore look like absence.&lt;/p&gt;
&lt;h2 id="step-by-step-verification"&gt;
&lt;a href="#step-by-step-verification" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Step-by-step verification
&lt;/h2&gt;
&lt;p&gt;First prove the target identity, then describe the exact API name and retain only nonsensitive fields:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org display --target-org &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf sobject describe --sobject MyObject__c --target-org &lt;span class="nb"&gt;test&lt;/span&gt; --json &amp;gt;describe.json
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;jq &lt;span class="s1"&gt;&amp;#39;{status, name: .result.name, queryable: .result.queryable,
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s1"&gt; createable: .result.createable, updateable: .result.updateable}&amp;#39;&lt;/span&gt; describe.json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Do not use a partial label or &lt;code&gt;DeveloperName&lt;/code&gt; where an object API name is required. Standard, custom, namespaced, and external objects have different suffixes. If the command fails, save the structured error, check the package/license, and compare with an administrator only to distinguish absence from permissions—do not simply grant broad access.&lt;/p&gt;
&lt;h2 id="alternatives-and-tradeoffs"&gt;
&lt;a href="#alternatives-and-tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Alternatives and tradeoffs
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sf sobject describe&lt;/code&gt; is ideal for scripts and local diagnosis.&lt;/li&gt;
&lt;li&gt;Apex &lt;code&gt;Schema.getGlobalDescribe()&lt;/code&gt; keeps runtime feature detection inside the transaction but consumes describe resources and still requires security enforcement.&lt;/li&gt;
&lt;li&gt;Metadata listing helps inventory components but is heavier than an exact describe.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;p&gt;Replace legacy schema commands, make &lt;code&gt;--target-org&lt;/code&gt; mandatory, parse JSON rather than human text, and treat nonzero exit status as “unknown until classified,” not automatically “missing.” Test with an available object, an unavailable API name, and a user without access. Pin the CLI in CI and delete diagnostic JSON if it contains organization details.&lt;/p&gt;</description></item><item><title>Streamline Your TypeScript Projects with Advanced Path Mapping Techniques</title><link>https://www.yopa.page/blog/2023-04-24-streamline-your-typescript-projects-with-advanced-path-mapping-techniques.html</link><pubDate>Mon, 24 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-24-streamline-your-typescript-projects-with-advanced-path-mapping-techniques.html</guid><description>
&lt;h2 id="runtime-resolution-matters"&gt;
&lt;a href="#runtime-resolution-matters" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Runtime resolution matters
&lt;/h2&gt;
&lt;p&gt;TypeScript &lt;code&gt;paths&lt;/code&gt; changes compiler resolution only; it does not rewrite emitted import specifiers or teach Node.js how to load an alias. Configure the runtime or bundler too, or use package &lt;code&gt;imports&lt;/code&gt;/&lt;code&gt;exports&lt;/code&gt; where appropriate. Verify the built output in the same environment that runs it.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re a TypeScript developer, you may have encountered the &amp;ldquo;Cannot find module&amp;rdquo; error when importing a module from a relative or absolute path. This can be frustrating, especially when dealing with a large codebase.&lt;/p&gt;
&lt;h2 id="path-mapping"&gt;
&lt;a href="#path-mapping" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Path Mapping
&lt;/h2&gt;
&lt;p&gt;In TypeScript, &lt;strong&gt;Path Mapping&lt;/strong&gt; is a feature that allows you to specify aliases for module paths. This can be helpful when working with a large codebase with multiple directories and files. Path Mapping helps to simplify the importation process by allowing you to use a custom alias instead of a relative or absolute path.&lt;/p&gt;
&lt;p&gt;For example, instead of using:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;someModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;../../path/to/module&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You can use:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;someModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@customAlias/module&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="setting-up-path-mapping"&gt;
&lt;a href="#setting-up-path-mapping" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Setting up Path Mapping
&lt;/h2&gt;
&lt;p&gt;To use Path Mapping in TypeScript, you need to add a &amp;ldquo;paths&amp;rdquo; property in your &lt;code&gt;tsconfig.json&lt;/code&gt; file.
Here&amp;rsquo;s an example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;compilerOptions&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;baseUrl&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./src&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;paths&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;@customAlias/*&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;app/*&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="s2"&gt;&amp;#34;@anotherAlias/*&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;../another-dir/*&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, we&amp;rsquo;ve added two Path Mapping aliases: &amp;ldquo;@customAlias&amp;rdquo; and &amp;ldquo;@anotherAlias&amp;rdquo;. The first alias maps to the &amp;ldquo;app&amp;rdquo; directory inside the &amp;ldquo;src&amp;rdquo; directory, and the second alias maps to the &amp;ldquo;another-dir&amp;rdquo; directory that&amp;rsquo;s located one level up from the current directory.&lt;/p&gt;
&lt;p&gt;Once you&amp;rsquo;ve set up your Path Mapping aliases, you can use them in your import statements like this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;someModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@customAlias/module&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;anotherModule&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;@anotherAlias/module&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;And that&amp;rsquo;s it! You&amp;rsquo;ve successfully set up Path Mapping in TypeScript and can now &amp;ldquo;Se3y&amp;rdquo; your imports.&lt;/p&gt;</description></item><item><title>The Imposter, Not Imposter Syndrome in Software Developers</title><link>https://www.yopa.page/blog/2023-04-23-the-imposter-not-imposter-syndrome-in-software-developers.html</link><pubDate>Sun, 23 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-23-the-imposter-not-imposter-syndrome-in-software-developers.html</guid><description>
&lt;p&gt;While imposter syndrome is a legitimate issue that affects many people, there is another type of imposter in the software development world that doesn&amp;rsquo;t get talked about enough: the actual imposter.&lt;/p&gt;
&lt;p&gt;Yes, you read that right. There are people out there who claim to be software developers, but in reality, they don&amp;rsquo;t know the first thing about programming. They&amp;rsquo;re the ones who somehow managed to convince someone to give them a job, and now they&amp;rsquo;re struggling to keep up with the rest of the team, or worse, they&amp;rsquo;re holding the team back.&lt;/p&gt;
&lt;p&gt;You might think that these people would be easy to spot, but that&amp;rsquo;s not always the case. Some of them are great at faking it until they make it, while others are just really good at hiding their lack of knowledge.&lt;/p&gt;
&lt;h2 id="the-signs-of-an-imposter"&gt;
&lt;a href="#the-signs-of-an-imposter" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Signs of an Imposter
&lt;/h2&gt;
&lt;p&gt;So, how can you tell if someone is an imposter? Here are a few signs to watch out for:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;They&amp;rsquo;re always asking for help: While it&amp;rsquo;s normal for developers to ask for help from time to time, an imposter will constantly need assistance with even the most basic tasks. They may also struggle to explain what they&amp;rsquo;re working on or how they plan to fix an issue.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;They don&amp;rsquo;t know basic coding concepts: An imposter may not understand the difference between a function and a method or have trouble understanding the fundamentals of object-oriented programming.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;They&amp;rsquo;re always behind schedule: Because they don&amp;rsquo;t know what they&amp;rsquo;re doing, an imposter will often take much longer to complete tasks than the rest of the team.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;They make excuses: When an imposter is caught not knowing something, they may try to deflect by blaming someone else or making excuses for why they don&amp;rsquo;t know something.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;An impostor may not be engaged in actual development tasks, instead they could be occupying themselves with activities such as assuming the role of a Product Manager or carrying out tasks that are entirely unrelated to development, for instance, creating documentation.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The worst part is, they don&amp;rsquo;t know they&amp;rsquo;re an imposter: An imposter may not realize that they don&amp;rsquo;t know what they&amp;rsquo;re doing. They may think they&amp;rsquo;re doing a good job, but in reality, they&amp;rsquo;re just getting by.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="how-to-deal-with-an-imposter"&gt;
&lt;a href="#how-to-deal-with-an-imposter" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
How to Deal with an Imposter
&lt;/h2&gt;
&lt;p&gt;I suggest two ways to deal with imposters:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Be honest&lt;/strong&gt;: If you suspect that someone is an imposter, be honest with them. Tell them that you think they&amp;rsquo;re struggling and that you&amp;rsquo;re willing to help them learn. If they&amp;rsquo;re willing to learn, great! If not, you may need to consider other options. which leads me to my next point&amp;hellip;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Don&amp;rsquo;t be afraid to remove them&lt;/strong&gt;: In the event that the imposter is impeding your productivity or hindering progress, it might be necessary to release them from the team. Though terminating an employee is not a preferred course of action, it is more beneficial than allowing them to become an obstacle to the team&amp;rsquo;s success.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are the imposter, I hope you will take this as a wake-up call. You may be able to fake it for a while, but eventually, you will be found out. If you want to be a software developer, you need to learn how to code. If you don&amp;rsquo;t know how to code, you need to learn how to code. If you don&amp;rsquo;t want to learn how to code, you need to find a different job.&lt;/p&gt;
&lt;p&gt;I am taking my job very seriously, and I expect you to do the same. If you want to be a REAL software developer, you need to EARN IT, don&amp;rsquo;t fake it.&lt;/p&gt;
&lt;p&gt;Cheers! 🍺&lt;/p&gt;</description></item><item><title>Named vs. Default Imports in TypeScript</title><link>https://www.yopa.page/blog/2023-04-22-named-vs-default-imports-in-typescript.html</link><pubDate>Sat, 22 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-22-named-vs-default-imports-in-typescript.html</guid><description>
&lt;h1 id="named-vs-default-imports-in-typescript"&gt;
&lt;a href="#named-vs-default-imports-in-typescript" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Named vs. Default Imports in TypeScript
&lt;/h1&gt;
&lt;p&gt;TypeScript provides two different ways to import functionality from a module: named imports and default imports. While both types of imports allow you to use code from other modules in your application, they have some key differences in terms of syntax and behavior.&lt;/p&gt;
&lt;h2 id="named-imports"&gt;
&lt;a href="#named-imports" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Named Imports
&lt;/h2&gt;
&lt;p&gt;A named import allows you to selectively import specific functionality from a module using a named identifier. You can import multiple named exports from a module by separating them with commas inside a set of curly braces &lt;code&gt;{}&lt;/code&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Import a single named export
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./math&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Use the imported function
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Output: 5
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="default-imports"&gt;
&lt;a href="#default-imports" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Default Imports
&lt;/h2&gt;
&lt;p&gt;A default import allows you to import a single default export from a module using any identifier you choose. You can only have one default export per module.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Import the default export
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;math&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./math&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Use the imported function
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Output: 5
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, we use a default import to import the &lt;strong&gt;default&lt;/strong&gt; export from the math module. Since the default export is an object with a &lt;code&gt;sum&lt;/code&gt; property that contains the &lt;code&gt;sum&lt;/code&gt; function, we can access the &lt;code&gt;sum&lt;/code&gt; function using dot notation.&lt;/p&gt;
&lt;h2 id="exporting-named-and-default-exports"&gt;
&lt;a href="#exporting-named-and-default-exports" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Exporting Named and Default Exports
&lt;/h2&gt;
&lt;p&gt;A single named export: &lt;code&gt;sum&lt;/code&gt;&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// math.ts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;A single default export:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// math.ts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>Promises in TypeScript</title><link>https://www.yopa.page/blog/2023-04-21-promises-in-typescript.html</link><pubDate>Fri, 21 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-21-promises-in-typescript.html</guid><description>
&lt;h2 id="what-is-a-promise"&gt;
&lt;a href="#what-is-a-promise" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What is a Promise?
&lt;/h2&gt;
&lt;p&gt;A Promise is an object that represents a value that may not be available yet, but will be at some point in the future. It provides a way to handle asynchronous operations in a more structured and readable way than using callbacks.
It has three states:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Pending: The initial state of the Promise.&lt;/li&gt;
&lt;li&gt;Fulfilled: The state of the Promise representing a successful operation.&lt;/li&gt;
&lt;li&gt;Rejected: The state of the Promise representing a failed operation.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="creating-a-promise"&gt;
&lt;a href="#creating-a-promise" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Creating a Promise
&lt;/h2&gt;
&lt;p&gt;In TypeScript, we can create a Promise using the Promise constructor. The constructor takes a single argument, which is a function that will be executed immediately when the Promise is created. This function takes two arguments: a &lt;code&gt;resolve&lt;/code&gt; function and a &lt;code&gt;reject&lt;/code&gt; function.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;ms&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;void&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, we&amp;rsquo;ve defined a function called &lt;code&gt;delay&lt;/code&gt; that takes a number of milliseconds as an argument. The function returns a new &lt;code&gt;Promise&lt;/code&gt; that resolves after the specified number of milliseconds using &lt;code&gt;setTimeout&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="consuming-a-promise"&gt;
&lt;a href="#consuming-a-promise" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Consuming a Promise
&lt;/h2&gt;
&lt;p&gt;Once we&amp;rsquo;ve created a &lt;code&gt;Promise&lt;/code&gt;, we need to be able to consume it. We can do this using the &lt;code&gt;then&lt;/code&gt; method, which takes two callback functions: one to handle the fulfillment of the Promise, and one to handle the rejection.&lt;/p&gt;
&lt;p&gt;Here is an example of how to consume the &lt;code&gt;delay&lt;/code&gt; Promise we defined earlier:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Promise fulfilled!&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Promise rejected: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, we&amp;rsquo;re calling the &lt;code&gt;delay&lt;/code&gt; function with a delay of 1000 milliseconds. We then attach a &lt;code&gt;then&lt;/code&gt; callback that logs a message to the console when the Promise is fulfilled, and a &lt;code&gt;catch&lt;/code&gt; callback that logs an error message if the Promise is rejected.&lt;/p&gt;
&lt;h2 id="chaining-promises"&gt;
&lt;a href="#chaining-promises" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Chaining Promises
&lt;/h2&gt;
&lt;p&gt;This allows us to write code that executes multiple asynchronous operations in sequence, without getting stuck in callback hell.&lt;/p&gt;
&lt;p&gt;To chain Promises, we use the &lt;code&gt;then&lt;/code&gt; method. When we attach a &lt;code&gt;then&lt;/code&gt; callback to a Promise, we return a new Promise that will be resolved with the return value of the callback. This allows us to chain multiple &lt;code&gt;then&lt;/code&gt; callbacks together, with each one receiving the result of the previous callback.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-ts" data-lang="ts"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;y&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;y&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;delayAdd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ms&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;((&lt;/span&gt;&lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;setTimeout&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="nx"&gt;ms&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;x&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;delayAdd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;then&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Result&lt;/span&gt;: &lt;span class="kt"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="c1"&gt;// Output: Result: 6
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Promise&lt;/span&gt; &lt;span class="nx"&gt;rejected&lt;/span&gt;: &lt;span class="kt"&gt;$&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, we&amp;rsquo;ve defined two functions: &lt;code&gt;add&lt;/code&gt;, which takes a number &lt;code&gt;x&lt;/code&gt; and returns a function that takes another number &lt;code&gt;y&lt;/code&gt; and returns the sum of &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt;, and &lt;code&gt;delayAdd&lt;/code&gt;, which takes a number &lt;code&gt;x&lt;/code&gt; and a delay time in milliseconds and returns a Promise that resolves with the value of &lt;code&gt;x&lt;/code&gt; after the specified delay time.&lt;/p&gt;
&lt;p&gt;We then chain these two functions together using the &lt;code&gt;then&lt;/code&gt; method. We call &lt;code&gt;delayAdd&lt;/code&gt; with &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;1000&lt;/code&gt;, which returns a Promise that resolves with the value &lt;code&gt;1&lt;/code&gt; after a delay of 1000 milliseconds. We then attach a &lt;code&gt;then&lt;/code&gt; callback to the Promise that calls &lt;code&gt;add(5)&lt;/code&gt; with the result of the previous Promise, which returns a new Promise that resolves with the sum of &lt;code&gt;1&lt;/code&gt; and &lt;code&gt;5&lt;/code&gt;. Finally, we attach another &lt;code&gt;then&lt;/code&gt; callback that logs the result to the console.&lt;/p&gt;</description></item><item><title>Salesforce CLI Deployment and Source Tracking</title><link>https://www.yopa.page/blog/2026-08-01-salesforce-cli-deploy-and-source-tracking.html</link><pubDate>Thu, 20 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-salesforce-cli-deploy-and-source-tracking.html</guid><description>
&lt;p&gt;Verified on 2026-08-01. The old &lt;code&gt;sfdx force:source:push&lt;/code&gt; comparison is obsolete because the &lt;code&gt;sfdx&lt;/code&gt; CLI has been unsupported since April 2023.&lt;/p&gt;
&lt;p&gt;Use one supported deployment surface:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf project deploy preview --source-dir force-app --target-org dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf project deploy start --source-dir force-app --target-org dev --dry-run
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf project deploy start --source-dir force-app --target-org dev
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Source tracking tells the CLI which local and org changes differ; it is not a substitute for Git. Scratch orgs commonly support tracking, while production delivery should be explicit and repeatable. In CI, pin the CLI version, pass &lt;code&gt;--target-org&lt;/code&gt;, choose an appropriate test level, validate first, and keep authentication outside the repository.&lt;/p&gt;
&lt;p&gt;Migration is mechanical: replace &lt;code&gt;force:source:push&lt;/code&gt; and &lt;code&gt;force:source:deploy&lt;/code&gt; scripts with &lt;code&gt;sf project deploy start&lt;/code&gt;, then verify the selected metadata using preview or dry-run. Direct source-format deployment is the default; convert to Metadata API format only for a consumer that requires it.&lt;/p&gt;
&lt;p&gt;References: &lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-plugin/guide/migrate-sfdx-sf.html"&gt;Salesforce CLI migration&lt;/a&gt;, &lt;a href="https://developer.salesforce.com/docs/platform/salesforce-cli-reference/guide/cli_reference.html"&gt;CLI reference&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="current-mental-model"&gt;
&lt;a href="#current-mental-model" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Current mental model
&lt;/h2&gt;
&lt;p&gt;Git answers what the team approved. Source tracking answers what differs between one local workspace and one tracking-enabled org. Deployment moves an explicitly selected set of metadata. Keeping those responsibilities separate prevents “push whatever changed” from becoming a release strategy.&lt;/p&gt;
&lt;h2 id="a-safe-release-path"&gt;
&lt;a href="#a-safe-release-path" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A safe release path
&lt;/h2&gt;
&lt;p&gt;Start by confirming the identity and inspecting conflicts:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf org display --target-org dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf project deploy preview --source-dir force-app --target-org dev
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;sf project deploy start --source-dir force-app --target-org dev &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; --dry-run --test-level RunLocalTests --json &amp;gt;deploy-validation.json
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Review the component set and test result before running the same selection without &lt;code&gt;--dry-run&lt;/code&gt;. For a curated release, use &lt;code&gt;--manifest manifest/package.xml&lt;/code&gt;; for a small fix, use repeated &lt;code&gt;--metadata&lt;/code&gt; or an exact source directory. Keep destructive changes in their supported manifest and require a separate approval. Never infer a production target from a developer&amp;rsquo;s default config.&lt;/p&gt;
&lt;h2 id="alternatives-and-tradeoffs"&gt;
&lt;a href="#alternatives-and-tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Alternatives and tradeoffs
&lt;/h2&gt;
&lt;p&gt;Source tracking is convenient in scratch orgs but can be confusing after manual changes or team sharing. An explicit manifest is auditable but must be maintained. Directory deployment is simple but may select more components than intended. DevOps Center or another delivery platform can add governance, yet the underlying scope, tests, identity, and rollback decisions remain.&lt;/p&gt;
&lt;h2 id="migration-checklist-and-verification"&gt;
&lt;a href="#migration-checklist-and-verification" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist and verification
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Inventory every legacy &lt;code&gt;push&lt;/code&gt;, &lt;code&gt;pull&lt;/code&gt;, &lt;code&gt;source:deploy&lt;/code&gt;, and &lt;code&gt;source:retrieve&lt;/code&gt; command.&lt;/li&gt;
&lt;li&gt;Map each to deploy/retrieve behavior; don&amp;rsquo;t translate names blindly.&lt;/li&gt;
&lt;li&gt;Make target org and test level explicit in CI.&lt;/li&gt;
&lt;li&gt;Validate in a representative nonproduction org and retain the report.&lt;/li&gt;
&lt;li&gt;Compare deployed component IDs with the reviewed manifest or directory.&lt;/li&gt;
&lt;li&gt;Test failure, conflict, and rollback procedures before production.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;After deployment, query the deployment result, run relevant smoke tests, and confirm that Git is still the source of truth rather than retrieving unreviewed org changes back over it.&lt;/p&gt;</description></item><item><title>TypeScript Decorators After TypeScript 5.0</title><link>https://www.yopa.page/blog/2026-08-01-typescript-decorators.html</link><pubDate>Wed, 19 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-typescript-decorators.html</guid><description>
&lt;p&gt;TypeScript now has two decorator models that look similar but have different call signatures and capabilities. TypeScript 5.0 introduced support for the current ECMAScript decorator proposal without requiring &lt;code&gt;experimentalDecorators&lt;/code&gt;. The older experimental model remains available for frameworks that depend on it.&lt;/p&gt;
&lt;p&gt;This article was verified on 2026-08-01 against the &lt;a href="https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-0.html#decorators"&gt;TypeScript 5.0 release notes&lt;/a&gt; and the &lt;a href="https://www.typescriptlang.org/docs/handbook/decorators"&gt;legacy decorators reference&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="a-standard-method-decorator"&gt;
&lt;a href="#a-standard-method-decorator" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A standard method decorator
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;loggedMethod&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;This&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Args&lt;/span&gt; &lt;span class="na"&gt;extends&lt;/span&gt; &lt;span class="na"&gt;unknown&lt;/span&gt;&lt;span class="err"&gt;[]&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Return&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;This&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;: &lt;span class="kt"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Return&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;context&lt;/span&gt;: &lt;span class="kt"&gt;ClassMethodDecoratorContext&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;This&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;This&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;: &lt;span class="kt"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;Return&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;This&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;: &lt;span class="kt"&gt;Args&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`calling &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;context&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;target&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;args&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Greeter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;@loggedMethod&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sb"&gt;`Hello, &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The decorator receives the decorated value and a context object. Keep decorators small and explicit: changing initialization order, binding, or public behavior can make a class difficult to reason about.&lt;/p&gt;
&lt;p&gt;Standard decorators can return a replacement value of the appropriate kind. They can also use &lt;code&gt;context.addInitializer&lt;/code&gt; when initialization work is necessary. The type parameters in the example preserve the original method&amp;rsquo;s &lt;code&gt;this&lt;/code&gt;, argument, and return types instead of erasing them with &lt;code&gt;any&lt;/code&gt;. That makes the wrapper useful without weakening the class&amp;rsquo;s public contract.&lt;/p&gt;
&lt;p&gt;Decorators execute while a class is being defined, and their order matters when several are stacked. Avoid network calls, filesystem access, or other unpredictable work at definition time. A decorator should not quietly turn a synchronous method into an asynchronous one or change its error contract. If the behavior cannot be described and tested locally, an ordinary function or explicit composition is often the clearer design.&lt;/p&gt;
&lt;h2 id="standard-versus-legacy"&gt;
&lt;a href="#standard-versus-legacy" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Standard versus legacy
&lt;/h2&gt;
&lt;p&gt;Standard decorators are the default choice for new code. Legacy decorators use &lt;code&gt;experimentalDecorators&lt;/code&gt; and signatures such as &lt;code&gt;(target, propertyKey, descriptor)&lt;/code&gt;. They are not type-compatible with the standard model. The standard proposal also does not support parameter decorators or &lt;code&gt;emitDecoratorMetadata&lt;/code&gt; in the same way.&lt;/p&gt;
&lt;p&gt;That boundary matters for Angular, NestJS, TypeORM, and other libraries that may depend on legacy metadata. Follow the framework&amp;rsquo;s supported configuration instead of mechanically removing &lt;code&gt;experimentalDecorators&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="why-the-old-tutorial-is-archived"&gt;
&lt;a href="#why-the-old-tutorial-is-archived" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why the old tutorial is archived
&lt;/h2&gt;
&lt;p&gt;The earlier article used the legacy class-decorator signature as though it were the only TypeScript model. That code can still be correct inside a deliberately configured legacy project, but it gives new readers the wrong default and omits the most important compatibility boundary. A partial edit would leave examples whose meaning changes with &lt;code&gt;tsconfig.json&lt;/code&gt;, so the original is preserved as an archive and this guide starts from the current mental model.&lt;/p&gt;
&lt;h2 id="alternatives-and-tradeoffs"&gt;
&lt;a href="#alternatives-and-tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Alternatives and tradeoffs
&lt;/h2&gt;
&lt;p&gt;Use a decorator when declarative syntax materially improves a repeated, cross-cutting operation and the team understands its lifecycle. Common examples include framework registration, validation metadata, or a small method wrapper.&lt;/p&gt;
&lt;p&gt;Prefer an ordinary higher-order function when the transformation does not need class syntax. Prefer constructor injection or explicit composition when wiring services. Prefer a factory when object creation is the real concern. These alternatives are easier to call, debug, and test because control flow remains visible.&lt;/p&gt;
&lt;p&gt;Legacy decorators may remain the right choice when a supported framework requires them. The tradeoff is coupling compiler options and metadata behavior to that framework. Standard decorators align with the current language direction but cannot be substituted mechanically for legacy parameter decorators or reflection-based dependency injection.&lt;/p&gt;
&lt;h2 id="safe-adoption-example"&gt;
&lt;a href="#safe-adoption-example" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Safe adoption example
&lt;/h2&gt;
&lt;p&gt;Add one decorator behind behavior tests before applying it across a codebase:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;strict&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;assert&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;node:assert&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;greeter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Greeter&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;assert&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;greeter&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Ada&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Hello, Ada&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Check the generated JavaScript target used by the application and test both normal results and thrown errors. If a decorator binds methods through an initializer, also test detached callbacks and subclass behavior. A compile-only check will not detect every runtime-order problem.&lt;/p&gt;
&lt;h2 id="migration-checklist"&gt;
&lt;a href="#migration-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration checklist
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Inventory decorators and metadata consumers.&lt;/li&gt;
&lt;li&gt;Check framework and library documentation for standard-decorator support.&lt;/li&gt;
&lt;li&gt;Create a small compilation test before changing &lt;code&gt;tsconfig.json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Rewrite signatures and add behavior tests for initialization and method calls.&lt;/li&gt;
&lt;li&gt;Remove legacy flags only after all consumers have migrated.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;For a library, publish the decorator model and required TypeScript range as part of the public compatibility contract. For an application, pin TypeScript and review compiler release notes before upgrades. Do not enable &lt;code&gt;emitDecoratorMetadata&lt;/code&gt; merely because an unrelated example does; emitted runtime type information increases coupling and is part of the legacy ecosystem.&lt;/p&gt;
&lt;p&gt;Verify with the project&amp;rsquo;s pinned compiler:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npx tsc --noEmit
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Inspect &lt;code&gt;tsconfig.json&lt;/code&gt; in the same review. A passing command from a different directory or compiler version does not prove which decorator model the real build used.&lt;/p&gt;</description></item><item><title>Recognizing a Premature Abstraction</title><link>https://www.yopa.page/blog/2023-04-18-the-immature-abstraction-problem-in-software-engineering.html</link><pubDate>Tue, 18 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-18-the-immature-abstraction-problem-in-software-engineering.html</guid><description>
&lt;p&gt;An abstraction is premature when it is created before the code provides enough evidence for a stable shared concept. It often begins with two pieces of code that look similar but change for different reasons.&lt;/p&gt;
&lt;p&gt;The problem is not abstraction itself. A good abstraction gives related behavior one name, establishes a useful boundary, and lets callers depend on a smaller contract. The problem appears when the contract is based on a guess. Each new use case then adds flags, optional parameters, or special branches until the shared code is harder to change than the original duplication.&lt;/p&gt;
&lt;h2 id="signals-to-look-for"&gt;
&lt;a href="#signals-to-look-for" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Signals to look for
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Callers pass booleans that switch the abstraction between unrelated modes.&lt;/li&gt;
&lt;li&gt;A generic name hides important domain differences.&lt;/li&gt;
&lt;li&gt;One consumer needs most of the interface while another implements empty or throwing methods.&lt;/li&gt;
&lt;li&gt;A small requirement change repeatedly modifies the shared abstraction and several unrelated callers.&lt;/li&gt;
&lt;li&gt;Tests mostly exercise branching inside the abstraction instead of stable behavior at its boundary.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="a-practical-response"&gt;
&lt;a href="#a-practical-response" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A practical response
&lt;/h2&gt;
&lt;p&gt;It is reasonable to keep a little duplication while the requirements are still diverging. When a third case appears—or when two cases have changed together more than once—compare their responsibilities and invariants. Extract the smallest concept that has a clear name and a testable contract.&lt;/p&gt;
&lt;p&gt;If an existing abstraction has accumulated exceptions, first add characterization tests. Separate the consumers that change for different reasons, simplify each path, and then look again for the smaller stable concept. The goal is not to eliminate every repeated line; it is to make future changes local and understandable.&lt;/p&gt;
&lt;p&gt;Before extracting, ask:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;What specific change becomes easier after this extraction?&lt;/li&gt;
&lt;li&gt;Which behavior and invariants are genuinely shared?&lt;/li&gt;
&lt;li&gt;Can a caller understand the contract without knowing every implementation?&lt;/li&gt;
&lt;li&gt;What evidence would tell us that the abstraction should be split again?&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The best time to abstract is not determined by a fixed duplication count. It is when the code has revealed a stable responsibility and the new boundary makes that responsibility easier to test and change.&lt;/p&gt;</description></item><item><title>A Practical Python Environment Workflow in 2026</title><link>https://www.yopa.page/blog/2026-08-01-python-project-environments.html</link><pubDate>Mon, 17 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-python-project-environments.html</guid><description>
&lt;p&gt;Python environment management is easier when version selection and dependency isolation are treated as separate decisions. A version manager such as &lt;code&gt;pyenv&lt;/code&gt; can select an interpreter. Python&amp;rsquo;s built-in &lt;code&gt;venv&lt;/code&gt; then creates an isolated environment for one project.&lt;/p&gt;
&lt;p&gt;This article was verified on 2026-08-01 against the &lt;a href="https://docs.python.org/3/library/venv.html"&gt;Python &lt;code&gt;venv&lt;/code&gt; documentation&lt;/a&gt; and the &lt;a href="https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/"&gt;Python Packaging User Guide&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="recommended-baseline"&gt;
&lt;a href="#recommended-baseline" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Recommended baseline
&lt;/h2&gt;
&lt;p&gt;Use a Python release that is still supported by the Python project. From the project root:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 --version
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python3 -m venv .venv
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m pip install --upgrade pip
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Activation is convenient but not required. Automation can call &lt;code&gt;.venv/bin/python&lt;/code&gt; directly. Keep &lt;code&gt;.venv/&lt;/code&gt; out of Git and record dependencies in project metadata or a reviewed requirements input rather than committing the environment itself.&lt;/p&gt;
&lt;h2 id="choosing-a-tool"&gt;
&lt;a href="#choosing-a-tool" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Choosing a tool
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Use &lt;code&gt;venv&lt;/code&gt; when you want the standard, minimal building block.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;pyenv&lt;/code&gt; when projects require different Python interpreters; pin the choice in &lt;code&gt;.python-version&lt;/code&gt; only if the team agrees to that convention.&lt;/li&gt;
&lt;li&gt;Use a workflow tool such as Hatch, PDM, Pipenv, Poetry, or uv when its locking, publishing, or task features solve a concrete project need. The PyPA does not prescribe one universal workflow tool.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;pipx&lt;/code&gt; for standalone Python command-line applications so each tool receives its own environment.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="migrating-an-older-project"&gt;
&lt;a href="#migrating-an-older-project" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migrating an older project
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Confirm the required Python version and that it is still supported.&lt;/li&gt;
&lt;li&gt;Preserve the dependency declaration before deleting any old environment.&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;.venv&lt;/code&gt; with the selected interpreter.&lt;/li&gt;
&lt;li&gt;Install from the project&amp;rsquo;s declared dependencies.&lt;/li&gt;
&lt;li&gt;Run tests and the application&amp;rsquo;s normal start command.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Do not assume that an environment created by one interpreter can be upgraded in place. Recreate it and verify it instead.&lt;/p&gt;
&lt;h2 id="why-the-older-recipe-changed"&gt;
&lt;a href="#why-the-older-recipe-changed" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why the older recipe changed
&lt;/h2&gt;
&lt;p&gt;Python 2 reached end of life in 2020, and its standard library did not provide the Python 3 &lt;code&gt;venv&lt;/code&gt; module described by modern tutorials. A command such as &lt;code&gt;python2 -m venv&lt;/code&gt; therefore was not a portable baseline. Older examples also tend to mix the operating-system Python, Homebrew Python, a version manager, and project packages without explaining which layer owns what.&lt;/p&gt;
&lt;p&gt;Keep the ownership boundaries explicit. The operating system or package manager owns its Python installation. A version manager may install additional interpreters for development. The project owns its dependency declaration. A disposable virtual environment contains installed artifacts. Never use &lt;code&gt;sudo pip install&lt;/code&gt; to make a project work, and do not modify an externally managed system interpreter to avoid creating an environment.&lt;/p&gt;
&lt;h2 id="a-reproducible-project-example"&gt;
&lt;a href="#a-reproducible-project-example" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A reproducible project example
&lt;/h2&gt;
&lt;p&gt;For a small application, create &lt;code&gt;pyproject.toml&lt;/code&gt; with its direct requirements and build metadata rather than treating a &lt;code&gt;pip freeze&lt;/code&gt; snapshot as design intent. The exact content depends on the selected build backend. Once the repository has chosen a workflow, document one bootstrap command and keep CI aligned with it.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;git clone https://example.com/team/project.git
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;cd&lt;/span&gt; project
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pyenv install --skip-existing 3.13.5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;pyenv &lt;span class="nb"&gt;local&lt;/span&gt; 3.13.5
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m venv .venv
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.venv/bin/python -m pip install -r requirements.txt
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;.venv/bin/python -m pytest
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The concrete version above is an example, not a timeless recommendation. Check the &lt;a href="https://devguide.python.org/versions/"&gt;Python versions page&lt;/a&gt; and the project&amp;rsquo;s declared support range before selecting it. In CI, create a fresh environment for every supported Python version instead of copying a developer&amp;rsquo;s &lt;code&gt;.venv&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="common-failure-modes"&gt;
&lt;a href="#common-failure-modes" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Common failure modes
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;If &lt;code&gt;python&lt;/code&gt; still resolves outside &lt;code&gt;.venv&lt;/code&gt;, inspect &lt;code&gt;command -v python&lt;/code&gt; and use the environment&amp;rsquo;s absolute interpreter path.&lt;/li&gt;
&lt;li&gt;If native extensions fail, confirm compiler and operating-system prerequisites; recreating the environment cannot supply missing system libraries.&lt;/li&gt;
&lt;li&gt;If two developers resolve different versions, adopt the project&amp;rsquo;s chosen lock or constraints workflow rather than exchanging environment directories.&lt;/li&gt;
&lt;li&gt;If a command-line tool conflicts with project dependencies, install it through &lt;code&gt;pipx&lt;/code&gt; instead of the project environment.&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;.venv&lt;/code&gt; was copied or the interpreter moved, recreate it. Virtual environments are generally disposable and not designed to be portable archives.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Activation scripts execute shell code. Use the activation script created by the environment you control, and do not source an environment directory obtained from an untrusted archive.&lt;/p&gt;
&lt;h2 id="verification"&gt;
&lt;a href="#verification" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Verification
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -c &lt;span class="s1"&gt;&amp;#39;import sys; print(sys.executable); print(sys.version)&amp;#39;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m pip check
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The executable should resolve inside &lt;code&gt;.venv&lt;/code&gt;, and &lt;code&gt;pip check&lt;/code&gt; should report no broken installed requirements.&lt;/p&gt;
&lt;p&gt;Finally, run the repository&amp;rsquo;s full test, lint, type-check, build, and smoke-test commands. Environment creation is successful only when a clean machine can reproduce the same supported workflow.&lt;/p&gt;</description></item><item><title>Iterator vs Iterable in TypeScript</title><link>https://www.yopa.page/blog/2023-04-16-iterator-vs-iterable-in-typescript.html</link><pubDate>Sun, 16 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-16-iterator-vs-iterable-in-typescript.html</guid><description>
&lt;h2 id="iterator-vs-iterable-in-typescript"&gt;
&lt;a href="#iterator-vs-iterable-in-typescript" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Iterator vs Iterable in TypeScript
&lt;/h2&gt;
&lt;p&gt;In TypeScript, an iterator is an object that provides a sequence of values through a series of iterations. An iterable, on the other hand, is an object that can be iterated over using an iterator.&lt;/p&gt;
&lt;h2 id="iterators"&gt;
&lt;a href="#iterators" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Iterators
&lt;/h2&gt;
&lt;p&gt;In TypeScript, an iterator is an object that implements the &lt;code&gt;Iterator protocol&lt;/code&gt;. The Iterator protocol defines a single method called &lt;code&gt;next()&lt;/code&gt;, which returns an object with two properties: &lt;code&gt;value&lt;/code&gt; and &lt;code&gt;done&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;value&lt;/code&gt; property is the next value in the sequence, and the &lt;code&gt;done&lt;/code&gt; property is a boolean value that indicates whether the sequence has ended. When the &lt;code&gt;done&lt;/code&gt; property is &lt;code&gt;true&lt;/code&gt;, there are no more values in the sequence.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example of an iterator that produces the sequence of numbers 1 to 5:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numberIterator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;next&lt;/span&gt;: &lt;span class="kt"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;done&lt;/span&gt;: &lt;span class="kt"&gt;value&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, the &lt;code&gt;next()&lt;/code&gt; method returns an object with the &lt;code&gt;value&lt;/code&gt; property set to the current number, and the &lt;code&gt;done&lt;/code&gt; property set to false if there are more numbers in the sequence, or true if the sequence has ended.&lt;/p&gt;
&lt;h2 id="iterables"&gt;
&lt;a href="#iterables" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Iterables
&lt;/h2&gt;
&lt;p&gt;In TypeScript, an &lt;code&gt;iterable&lt;/code&gt; is an object that implements the &lt;code&gt;Iterable&lt;/code&gt; protocol. The &lt;code&gt;Iterable&lt;/code&gt; protocol defines a single method called Symbol.iterator, which returns an iterator object.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example of an iterable that produces the same sequence of numbers as the iterator above:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;numberIterable&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;Symbol&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;iterator&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;next&lt;/span&gt;: &lt;span class="kt"&gt;function&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;done&lt;/span&gt;: &lt;span class="kt"&gt;value&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, the &lt;code&gt;numberIterable&lt;/code&gt; object implements the &lt;code&gt;Symbol.iterator&lt;/code&gt; method, which returns an iterator object with the same implementation as the numberIterator object in the previous example.&lt;/p&gt;
&lt;h2 id="using-iterators-and-iterables"&gt;
&lt;a href="#using-iterators-and-iterables" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Using Iterators and Iterables
&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Iterators&lt;/code&gt; and &lt;code&gt;iterables&lt;/code&gt; are commonly used in TypeScript and JavaScript to iterate over arrays, sets, and maps.&lt;/p&gt;
&lt;p&gt;For example, here&amp;rsquo;s how you can use the &lt;code&gt;numberIterator&lt;/code&gt; object to iterate over the sequence of numbers it produces:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;iteratorResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;numberIterator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;iteratorResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;iteratorResult&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;iteratorResult&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;numberIterator&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;next&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;And here&amp;rsquo;s how you can use the &lt;code&gt;numberIterable&lt;/code&gt; object to iterate over the same sequence of numbers:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="kt"&gt;number&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;numberIterable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>Mocking Modules and Functions with Jest</title><link>https://www.yopa.page/blog/2023-04-15-mocking-modules-and-functions-with-jest--copy.html</link><pubDate>Sat, 15 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-15-mocking-modules-and-functions-with-jest--copy.html</guid><description>
&lt;h2 id="mocking-modules-and-functions-with-jest"&gt;
&lt;a href="#mocking-modules-and-functions-with-jest" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Mocking Modules and Functions with Jest
&lt;/h2&gt;
&lt;p&gt;In this tutorial, we&amp;rsquo;ll explore how Jest can be used to Mock Modules and Functions. We&amp;rsquo;ll discuss the different techniques involved and provide examples that demonstrate their usage.&lt;/p&gt;
&lt;h2 id="using-jestfn-to-mock-functions"&gt;
&lt;a href="#using-jestfn-to-mock-functions" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Using jest.fn to Mock Functions
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;example&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;├──&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;└──&lt;/span&gt; &lt;span class="nx"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;js&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;jest.fn()&lt;/code&gt; method is used to create a Mock Function that can be used to test the behavior of dependent code. Let&amp;rsquo;s consider an example where we want to test a function that depends on another function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// math.ts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;subtract&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;multiply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// app.ts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./math&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;export&lt;/span&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;multiply&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In the above code, the &lt;code&gt;multiply()&lt;/code&gt; function depends on the &lt;code&gt;add()&lt;/code&gt; function. To test the &lt;code&gt;multiply()&lt;/code&gt; function, we can create a Mock Function for the &lt;code&gt;add() &lt;/code&gt;function using &lt;code&gt;jest.fn()&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// app.test.ts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;multiply&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./app&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./math&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;./math&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt;: &lt;span class="kt"&gt;jest.fn&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;multiply function calls add function&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mockedAdd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mocked&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;mockedAdd&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mockedAdd&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The module factory replaces &lt;code&gt;add&lt;/code&gt;, and &lt;code&gt;jest.mocked&lt;/code&gt; gives the import its mock-aware TypeScript type. Assigning directly to an imported ESM namespace is invalid because module bindings are read-only.&lt;/p&gt;
&lt;p&gt;This example assumes Jest&amp;rsquo;s transformed/CommonJS mocking flow. Native ESM projects have different loading rules; follow Jest&amp;rsquo;s current &lt;a href="https://jestjs.io/docs/ecmascript-modules"&gt;ECMAScript Modules guide&lt;/a&gt; and use &lt;code&gt;jest.unstable_mockModule&lt;/code&gt; where required.&lt;/p&gt;
&lt;h2 id="using-jestmock-to-mock-modules"&gt;
&lt;a href="#using-jestmock-to-mock-modules" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Using jest.mock to Mock Modules
&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;jest.mock()&lt;/code&gt; method is used to Mock entire modules, rather than just individual functions. This approach is useful when we want to Mock an entire module rather than just one or two functions.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s consider an example where we want to Mock an entire module that contains multiple functions:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// app.test.ts
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="kr"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;math&lt;/span&gt; &lt;span class="kr"&gt;from&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;./math&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;./math&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;add&lt;/span&gt;: &lt;span class="kt"&gt;jest.fn&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;subtract&lt;/span&gt;: &lt;span class="kt"&gt;jest.fn&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;multiply&lt;/span&gt;: &lt;span class="kt"&gt;jest.fn&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Mocking entire math module&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;subtract&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;multiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Here&amp;rsquo;s a step-by-step breakdown of what&amp;rsquo;s happening in this code:&lt;/p&gt;
&lt;p&gt;The first line imports all the functions exported by the math.js module using the import * as math from &amp;lsquo;./math&amp;rsquo;; syntax.&lt;/p&gt;
&lt;p&gt;The next line jest.mock Mocks the entire math.js module. The second argument of jest.mock is a function that returns an object containing Mock Functions for all the functions exported by the module. In this case, add, subtract, and multiply are all Mock Functions created using jest.fn().&lt;/p&gt;
&lt;p&gt;Next, the code sets the return values for the Mock Functions using mockReturnValue(). For example, math.add.mockReturnValue(3) sets the return value for the add function Mock to 3.&lt;/p&gt;
&lt;p&gt;Finally, the code calls the Mock Functions with arguments and asserts that the return values match the expected values using Jest&amp;rsquo;s expect function and the toBe matcher.&lt;/p&gt;</description></item><item><title>An Introduction to Jest Mock Functionality</title><link>https://www.yopa.page/blog/2023-04-14-an-introduction-to-jest-mock-functionality.html</link><pubDate>Fri, 14 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-14-an-introduction-to-jest-mock-functionality.html</guid><description>
&lt;p&gt;At times, when writing unit tests for your application, you may find it challenging to isolate the test subjects from their dependencies. &lt;code&gt;Mocking&lt;/code&gt; is a technique that can help you overcome this challenge. By replacing dependencies with objects that you can control and inspect, you can isolate the test subjects and test them in isolation.&lt;/p&gt;
&lt;p&gt;In this article, we will discuss the basics of mocking with &lt;strong&gt;Jest&lt;/strong&gt;, a popular testing framework, and explore different ways to use the Mock Function in Jest.&lt;/p&gt;
&lt;h2 id="what-is-mocking"&gt;
&lt;a href="#what-is-mocking" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What is Mocking?
&lt;/h2&gt;
&lt;p&gt;Mocking is a technique used in testing&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;to replace dependencies with objects that you can control and inspect. (Dependencies can be anything your subject relies on, but they are typically modules that the subject imports.)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-mock-function-in-jest"&gt;
&lt;a href="#the-mock-function-in-jest" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Mock Function in Jest
&lt;/h2&gt;
&lt;p&gt;When we talk about mocking in Jest, we&amp;rsquo;re typically referring to the Mock Function. The goal of mocking is to replace something we don’t control with something we do.&lt;/p&gt;
&lt;p&gt;The Mock Function in Jest provides features&amp;hellip;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;change the implementation.&lt;/li&gt;
&lt;li&gt;capture calls&lt;/li&gt;
&lt;li&gt;set return values&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="creating-a-mock-function-in-jest"&gt;
&lt;a href="#creating-a-mock-function-in-jest" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Creating a Mock Function in Jest
&lt;/h2&gt;
&lt;p&gt;The simplest way to create a Mock Function instance in Jest is with jest.fn(). With a Mock Function, we can easily test captured calls using Jest&amp;rsquo;s expect function:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;example of testing captured calls&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Assume we have a function `add` that adds two numbers.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// We want to test if `add` calls its arguments with the correct parameters.
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;add calls its arguments with the correct parameters&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mockAdd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;add&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;mockAdd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mockAdd&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;details&gt;
&lt;summary&gt;Explained&lt;/summary&gt;
In this example, we create a Mock Function `mockAdd` using `jest.fn()`, passing in the `add` function as its implementation. We then call `mockAdd` with arguments `2` and `3` and assert that it has been called with the correct parameters using Jest's `toHaveBeenCalledWith()` matcher. Finally, we assert that the result of `mockAdd(2, 3)` is `5`. This allows us to test that the `add` function is called with the correct parameters without having to call the real implementation of `add`.
&lt;/details&gt;
&lt;h2 id="changing-the-return-value-implementation-or-promise-resolution"&gt;
&lt;a href="#changing-the-return-value-implementation-or-promise-resolution" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Changing the Return Value, Implementation, or Promise Resolution
&lt;/h2&gt;
&lt;p&gt;Mock Functions in Jest can be used to change the return value, implementation, or promise resolution. Below are some examples of how to use the Mock Function to achieve this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;mock implementation&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;bar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, we create a Mock Function mock using jest.fn() and set its implementation to return &amp;ldquo;bar&amp;rdquo;. We then call mock with the argument &amp;ldquo;foo&amp;rdquo; and assert that it returns &amp;ldquo;bar&amp;rdquo; using Jest&amp;rsquo;s toBe() matcher. We also assert that mock has been called with the argument &amp;ldquo;foo&amp;rdquo; using Jest&amp;rsquo;s toHaveBeenCalledWith() matcher.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;mock implementation using mockImplementation&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;mockImplementation&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;bar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This example is similar to the previous one but uses the &lt;code&gt;mockImplementation()&lt;/code&gt; method to set the implementation of the Mock Function instead of passing it as an argument to jest.fn(). The result is the same: the implementation of mock is set to return &amp;ldquo;bar&amp;rdquo;, and we assert that it returns &amp;ldquo;bar&amp;rdquo; when called with the argument &amp;ldquo;foo&amp;rdquo;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;mock implementation one time using mockImplementationOnce&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nx"&gt;mockImplementationOnce&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;bar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;baz&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;baz&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, we use the &lt;code&gt;mockImplementationOnce()&lt;/code&gt; method to set the implementation of the Mock Function to return &amp;ldquo;bar&amp;rdquo; only the first time it is called. We call mock with &amp;ldquo;foo&amp;rdquo; and &amp;ldquo;baz&amp;rdquo; and assert that it returns &amp;ldquo;bar&amp;rdquo; when called with &amp;ldquo;foo&amp;rdquo;, and returns undefined when called with &amp;ldquo;baz&amp;rdquo;. We also assert that mock has been called with both arguments using Jest&amp;rsquo;s toHaveBeenCalledWith() matcher.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;mock return value using mockReturnValue&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, we create a Mock Function mock using &lt;code&gt;jest.fn()&lt;/code&gt; and set its return value to &amp;ldquo;bar&amp;rdquo; using the &lt;code&gt;mockReturnValue()&lt;/code&gt; method. We call mock with the argument &amp;ldquo;foo&amp;rdquo; and assert that it returns &amp;ldquo;bar&amp;rdquo; using Jest&amp;rsquo;s toBe() matcher. We also assert that mock has been called with the argument &amp;ldquo;foo&amp;rdquo; using Jest&amp;rsquo;s toHaveBeenCalledWith() matcher.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;test&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;mock promise resolution using mockResolvedValue&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;mock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;jest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;mockResolvedValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;resolves&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;toBe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;bar&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mock&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;foo&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, we are testing a function that returns a Promise. We want to make sure that the Promise resolves to the expected value, &amp;ldquo;bar&amp;rdquo;, and that it has been called with the expected argument, &amp;ldquo;foo&amp;rdquo;.
First, we create a Mock Function called mock using jest.fn(). We then use the mockResolvedValue() method to set the Promise resolution value of mock to &amp;ldquo;bar&amp;rdquo;.
Next, we call mock with the argument &amp;ldquo;foo&amp;rdquo;. Since mock returns a Promise, we use the resolves property to wait for the Promise to resolve, and then use the toBe() matcher to ensure that the resolved value is &amp;ldquo;bar&amp;rdquo;.
Finally, we use the toHaveBeenCalledWith() matcher to assert that mock has been called with the argument &amp;ldquo;foo&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s explore the topic of &amp;ldquo;Mocking Modules and Functions&amp;rdquo; tomorrow and learn how to use this powerful feature in our testing workflow.&lt;/p&gt;
&lt;p&gt;Tomorrow is Friday!!! YES!&lt;/p&gt;</description></item><item><title>Manage Node.js Versions Explained</title><link>https://www.yopa.page/blog/2023-04-13-manage_nodejs_versions_explained.html</link><pubDate>Thu, 13 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-13-manage_nodejs_versions_explained.html</guid><description>
&lt;p&gt;Managing Node.js versions can be challenging when working on multiple projects with different requirements. Fortunately, there are several tools and techniques available that can help you manage Node.js versions based on project requirements.&lt;/p&gt;
&lt;h2 id="why-manage-nodejs-versions"&gt;
&lt;a href="#why-manage-nodejs-versions" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why Manage Node.js Versions?
&lt;/h2&gt;
&lt;p&gt;One of the challenges of working with Node.js is managing the different versions of Node.js that are required for different projects. Different projects may have different dependencies, and these dependencies may require specific versions of Node.js.&lt;/p&gt;
&lt;p&gt;If you&amp;rsquo;re not careful, you may end up with a mess of different Node.js versions on your system, which can cause conflicts and make it difficult to manage your projects effectively.&lt;/p&gt;
&lt;h2 id="techniques-for-managing-nodejs-versions"&gt;
&lt;a href="#techniques-for-managing-nodejs-versions" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Techniques for Managing Node.js Versions
&lt;/h2&gt;
&lt;h2 id="current-support-policy"&gt;
&lt;a href="#current-support-policy" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Current support policy
&lt;/h2&gt;
&lt;p&gt;production applications should use a release currently listed as Active LTS or Maintenance LTS on the &lt;a href="https://nodejs.org/en/about/previous-releases"&gt;official Node.js release page&lt;/a&gt;. Pin the selected version for the project and use the same value in CI.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Node Version Manager (nvm)
Node Version Manager (nvm) is a command-line tool that allows you to manage multiple versions of Node.js on your system. With nvm, you can easily switch between different versions of Node.js and install new versions as needed.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Once you have nvm installed, you can use it to install and manage different versions of Node.js. For example, to install Node.js version lts, you can run the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nvm install --lts
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;To switch to this version, you can run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;nvm use --lts
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You can also use nvm to set a default Node.js version for a specific project by creating an .nvmrc file in the project directory. For example, to set the default version to 24, create an .nvmrc file with the following content:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;24&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now, when you navigate to the project directory and run nvm use, it will automatically switch to version 24.&lt;/p&gt;
&lt;ol start="2"&gt;
&lt;li&gt;Node.js Version Manager (n)
Node.js Version Manager (n) is a simple command-line tool that allows you to install and switch between different versions of Node.js. It is similar to nvm but has a simpler and more streamlined interface.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;To use n, you first need to install it on your system.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;npm install -g n
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Once you have n installed, you can use it to install and manage different versions of Node.js. For example, to install Node.js version 24, you can run the following command:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;n &lt;span class="m"&gt;24&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;To switch to this version, you can run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;n lts
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You can also use n to set a default Node.js version for a specific project by creating an .nvmrc file in the project directory, just like with nvm. For example, to set the default version to 24, create an .nvmrc file with the following content:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="m"&gt;24&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>Use console.time in TypeScript to troubleshoot performance issues</title><link>https://www.yopa.page/blog/2023-04-12-use-console.time-in-typescript-to-troubleshoot-performance-issues.html</link><pubDate>Wed, 12 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-12-use-console.time-in-typescript-to-troubleshoot-performance-issues.html</guid><description>
&lt;h2 id="consoletime"&gt;
&lt;a href="#consoletime" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
console.time()
&lt;/h2&gt;
&lt;p&gt;Performance issues can be a major headache for developers, especially when working with large-scale TypeScript projects. Fortunately, TypeScript provides several tools to help developers identify and troubleshoot performance issues, one of which is &lt;code&gt;console.time&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The &lt;code&gt;console.time()&lt;/code&gt; method in TypeScript allows developers to measure the time it takes for a block of code to execute. This is especially useful when trying to identify bottlenecks in performance, as it allows developers to isolate the code that is causing the issue and focus on optimizing it.&lt;/p&gt;
&lt;p&gt;To use &lt;code&gt;console.time()&lt;/code&gt; in TypeScript, simply call the method at the start of the block of code you want to measure, and pass a string argument that identifies the timer:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;myTimer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Code to measure goes here
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeEnd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;myTimer&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;console.timeEnd()&lt;/code&gt; method is then called at the end of the block of code, using the same timer identifier string. This will output the time elapsed between the two &lt;code&gt;console.time()&lt;/code&gt; calls to the console.&lt;/p&gt;
&lt;p&gt;For example, let&amp;rsquo;s say we have a function that sorts an array of numbers and we want to measure its performance:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;sortArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;time&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;sortArray&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;sortedArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;timeEnd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;sortArray&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;sortedArray&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, we use &lt;code&gt;console.time()&lt;/code&gt; to start a timer called &lt;strong&gt;sortArray&lt;/strong&gt; at the beginning of the function, and &lt;code&gt;console.timeEnd()&lt;/code&gt; to stop the timer at the end. The output of &lt;code&gt;console.timeEnd()&lt;/code&gt; will show the time elapsed between the two calls.&lt;/p&gt;
&lt;p&gt;By using &lt;code&gt;console.time()&lt;/code&gt; and &lt;code&gt;console.timeEnd()&lt;/code&gt; in this way, we can quickly identify which parts of our code are taking the longest to execute and focus our optimization efforts on those areas.&lt;/p&gt;</description></item><item><title>TypeScript's `Pick` and `Omit`</title><link>https://www.yopa.page/blog/2023-04-11-pick-and-omit-in-typescript.html</link><pubDate>Tue, 11 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-11-pick-and-omit-in-typescript.html</guid><description>
&lt;p&gt;TypeScript&amp;rsquo;s &lt;code&gt;Pick&lt;/code&gt; and &lt;code&gt;Omit&lt;/code&gt; are two utility types that allow you to create new types based on existing types. These types can be very useful in creating new types that are more specialized for a specific use case.&lt;/p&gt;
&lt;h2 id="pick"&gt;
&lt;a href="#pick" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Pick
&lt;/h2&gt;
&lt;p&gt;The Pick type allows you to create a new type by selecting only the specified properties from an existing type. Here&amp;rsquo;s an example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-TypeScript" data-lang="TypeScript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;age&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;email&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PersonNameAndAge&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Pick&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;name&amp;#34;&lt;/span&gt; &lt;span class="err"&gt;|&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;age&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// PersonNameAndAge is equivalent to:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// name: string;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// age: number;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, &lt;code&gt;PersonNameAndAge&lt;/code&gt; is a new type that only includes the &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;age&lt;/code&gt; properties from the &lt;code&gt;Person&lt;/code&gt; type. The syntax for using &lt;code&gt;Pick&lt;/code&gt; is as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Pick&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Keys&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Here, &lt;code&gt;Type&lt;/code&gt; is the type you want to pick properties from, and &lt;code&gt;Keys&lt;/code&gt; is a union of the keys you want to include in the new type.&lt;/p&gt;
&lt;h2 id="omit"&gt;
&lt;a href="#omit" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Omit
&lt;/h2&gt;
&lt;p&gt;The &lt;code&gt;Omit&lt;/code&gt; type allows you to create a new type by excluding the specified properties from an existing type. Here&amp;rsquo;s an example:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Person&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;age&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;email&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PersonWithoutEmail&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Omit&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;Person&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;email&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// PersonWithoutEmail is equivalent to:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// name: string;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// age: number;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// }
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, &lt;code&gt;PersonWithoutEmail&lt;/code&gt; is a new type that includes all the properties from the &lt;code&gt;Person&lt;/code&gt; type except for the &lt;code&gt;email&lt;/code&gt; property. The syntax for using &lt;code&gt;Omit&lt;/code&gt; is as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Omit&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;Type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Keys&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Here, &lt;code&gt;Type&lt;/code&gt; is the type you want to exclude properties from, and &lt;code&gt;Keys&lt;/code&gt; is a union of the keys you want to exclude from the new type.&lt;/p&gt;</description></item><item><title>Apex List Syntax and Nested Collections</title><link>https://www.yopa.page/blog/2026-08-01-apex-list-syntax-and-nested-collections.html</link><pubDate>Mon, 10 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2026-08-01-apex-list-syntax-and-nested-collections.html</guid><description>
&lt;p&gt;Verified against Salesforce documentation on 2026-08-01.&lt;/p&gt;
&lt;p&gt;Apex accepts both &lt;code&gt;String[]&lt;/code&gt; and &lt;code&gt;List&amp;lt;String&amp;gt;&lt;/code&gt; as syntax for a list. They are not two storage models, so choose &lt;code&gt;List&amp;lt;String&amp;gt;&lt;/code&gt; when readability matters.&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-apex" data-lang="apex"&gt;List&amp;lt;String&amp;gt; names = new List&amp;lt;String&amp;gt;{&amp;#39;Ada&amp;#39;, &amp;#39;Grace&amp;#39;};
String[] aliases = new String[]{&amp;#39;a&amp;#39;, &amp;#39;g&amp;#39;};
List&amp;lt;List&amp;lt;String&amp;gt;&amp;gt; groups = new List&amp;lt;List&amp;lt;String&amp;gt;&amp;gt;{names, aliases};
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;The older article was retired because its sample did not compile and described arrays as a separate, sequential-memory type. When migrating, replace ambiguous declarations with &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt;, give methods unique names, and run the Apex tests that exercise indexing and empty inputs. Array-style syntax remains useful when matching an existing codebase; &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt; makes nested generics clearer.&lt;/p&gt;
&lt;p&gt;Reference: &lt;a href="https://developer.salesforce.com/docs/atlas.en-us.apexcode.meta/apexcode/langCon_apex_collections_lists.htm"&gt;Apex Developer Guide: Lists&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="why-the-old-explanation-failed"&gt;
&lt;a href="#why-the-old-explanation-failed" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why the old explanation failed
&lt;/h2&gt;
&lt;p&gt;Apex deliberately treats array notation as an alternative spelling for &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt;. Reasoning from Java&amp;rsquo;s array memory model leads to false conclusions, while duplicate method signatures and undefined variables hide the real lesson behind compiler errors.&lt;/p&gt;
&lt;h2 id="a-practical-mental-model"&gt;
&lt;a href="#a-practical-mental-model" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A practical mental model
&lt;/h2&gt;
&lt;p&gt;A list is an ordered, zero-indexed collection that can grow with &lt;code&gt;add&lt;/code&gt;, replace an element with &lt;code&gt;set&lt;/code&gt;, and be iterated. Generic notation is clearer when types are nested. A set is better for uniqueness, and a map is better for keyed lookup; don&amp;rsquo;t choose a list merely because it is familiar.&lt;/p&gt;
&lt;h2 id="safe-example"&gt;
&lt;a href="#safe-example" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Safe example
&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code class="language-apex" data-lang="apex"&gt;public class NameGroups {
public static List&amp;lt;List&amp;lt;String&amp;gt;&amp;gt; group(List&amp;lt;String&amp;gt; input, Integer size) {
if (input == null || size == null || size &amp;lt;= 0) {
throw new IllegalArgumentException(&amp;#39;A positive group size is required&amp;#39;);
}
List&amp;lt;List&amp;lt;String&amp;gt;&amp;gt; result = new List&amp;lt;List&amp;lt;String&amp;gt;&amp;gt;();
for (Integer i = 0; i &amp;lt; input.size(); i += size) {
List&amp;lt;String&amp;gt; chunk = new List&amp;lt;String&amp;gt;();
for (Integer j = i; j &amp;lt; Math.min(i + size, input.size()); j++) chunk.add(input[j]);
result.add(chunk);
}
return result;
}
}
&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Test empty, null, exact-size, and remainder inputs. Remember that assigning one list variable to another shares the same mutable list; construct a new list when a shallow copy is intended.&lt;/p&gt;
&lt;h2 id="migration-and-verification-checklist"&gt;
&lt;a href="#migration-and-verification-checklist" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Migration and verification checklist
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;Replace claims about two different collection types with one List model.&lt;/li&gt;
&lt;li&gt;Normalize public APIs to &lt;code&gt;List&amp;lt;T&amp;gt;&lt;/code&gt; while allowing array notation in compatible callers.&lt;/li&gt;
&lt;li&gt;Give every method a unique signature and compile examples in a scratch org.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;List&amp;lt;List&amp;lt;T&amp;gt;&amp;gt;&lt;/code&gt; for nesting and add tests for mutation and bounds.&lt;/li&gt;
&lt;li&gt;Run Apex tests and static analysis before deployment.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Array notation can minimize churn in legacy code, but consistency and readable nested types usually outweigh that small benefit.&lt;/p&gt;</description></item><item><title>Understanding TypeScript's Record Type</title><link>https://www.yopa.page/blog/2023-04-09-typescript-understanding-record-type.html</link><pubDate>Sun, 09 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-09-typescript-understanding-record-type.html</guid><description>
&lt;p&gt;In TypeScript, the &lt;code&gt;Record&lt;/code&gt; type is a built-in utility type that represents an &lt;code&gt;object&lt;/code&gt; type whose &lt;code&gt;property&lt;/code&gt; keys are of a &lt;code&gt;specified type&lt;/code&gt; and whose property values are of a &lt;code&gt;specified type&lt;/code&gt;. It&amp;rsquo;s essentially an object type that maps keys of one type to values of another type.&lt;/p&gt;
&lt;p&gt;The syntax for &lt;code&gt;Record&lt;/code&gt; is as follows:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Copy&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;KeysType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;ValueType&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;where &lt;code&gt;KeysType&lt;/code&gt; is the type of the keys, and &lt;code&gt;ValueType&lt;/code&gt; is the type of the values.&lt;/p&gt;
&lt;h2 id="example-using-custom-types"&gt;
&lt;a href="#example-using-custom-types" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Example using custom types
&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s an example of how to use &lt;code&gt;Record&lt;/code&gt; with custom types to define an object type that maps an &lt;code&gt;enum&lt;/code&gt; to an object with properties of &lt;code&gt;specific types&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Copy&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;enum&lt;/span&gt; &lt;span class="nx"&gt;MyEnum&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;MyObject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;value&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;MyRecord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;MyEnum&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;MyObject&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myRecord&lt;/span&gt;: &lt;span class="kt"&gt;MyRecord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;MyEnum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;A&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;A&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;: &lt;span class="kt"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;MyEnum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;B&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;B&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;: &lt;span class="kt"&gt;2&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;MyEnum&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;C&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;C&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;: &lt;span class="kt"&gt;3&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, we define an enum &lt;code&gt;MyEnum&lt;/code&gt; and an object type &lt;code&gt;MyObject&lt;/code&gt; with two properties, name of type string and value of type number. We then define a &lt;code&gt;MyRecord&lt;/code&gt; type using &lt;code&gt;Record&lt;/code&gt; that maps &lt;code&gt;MyEnum&lt;/code&gt; keys to &lt;code&gt;MyObject&lt;/code&gt; values.&lt;/p&gt;
&lt;p&gt;We then create a value of type &lt;code&gt;MyRecord&lt;/code&gt; called &lt;code&gt;myRecord&lt;/code&gt; using object literals to set the values for each key.&lt;/p&gt;
&lt;h2 id="advanced-usage"&gt;
&lt;a href="#advanced-usage" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Advanced usage
&lt;/h2&gt;
&lt;h3 id="keyof--record-type"&gt;
&lt;a href="#keyof--record-type" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
keyof + Record type
&lt;/h3&gt;
&lt;p&gt;In TypeScript, the keyof operator is a type operator that can extract the keys of an object type as a union type. When combined with the Record type, it allows you to define an object type with specific key and value types based on the keys of an existing type.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s see the example.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;MyInterface&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;foo&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;bar&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;baz&lt;/span&gt;: &lt;span class="kt"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;MyRecord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Record&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;keyof&lt;/span&gt; &lt;span class="na"&gt;MyInterface&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="na"&gt;any&lt;/span&gt; &lt;span class="p"&gt;}&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myRecord&lt;/span&gt;: &lt;span class="kt"&gt;MyRecord&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;foo&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;: &lt;span class="kt"&gt;123&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;bar&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;hello&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;baz&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;value&lt;/span&gt;: &lt;span class="kt"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;details&gt;
&lt;summary&gt;Example Explained&lt;/summary&gt;
In this example, we define an interface `MyInterface` with three properties of different types. We then define a `MyRecord` type using `Record` and `keyof` that maps the keys of `MyInterface` to an object type with a single value property of type any.
&lt;p&gt;We then create a value of type &lt;code&gt;MyRecord&lt;/code&gt; called &lt;code&gt;myRecord&lt;/code&gt; using object literals to set the values for each key. This allows us to create an object with the same keys as &lt;code&gt;MyInterface&lt;/code&gt;, but with a uniform value property for each key.&lt;/p&gt;
&lt;/details&gt;</description></item><item><title>TypeScript - What is the difference between type and interface?</title><link>https://www.yopa.page/blog/2023-04-08-typescript-what-is-the-difference-between-type-and-interface.html</link><pubDate>Sat, 08 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-08-typescript-what-is-the-difference-between-type-and-interface.html</guid><description>
&lt;h2 id="type-vs-interface-when-to-use-each-one"&gt;
&lt;a href="#type-vs-interface-when-to-use-each-one" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Type vs Interface: When to Use Each One?
&lt;/h2&gt;
&lt;p&gt;While there is some overlap between &lt;code&gt;types&lt;/code&gt; and &lt;code&gt;interfaces&lt;/code&gt;, there are some &lt;strong&gt;key differences&lt;/strong&gt; that make one a better choice than the other in specific scenarios.&lt;/p&gt;
&lt;h3 id="use-types-when"&gt;
&lt;a href="#use-types-when" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Use &lt;code&gt;types&lt;/code&gt; when:
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;You need to create a &lt;code&gt;union&lt;/code&gt; or &lt;code&gt;intersection&lt;/code&gt; type.&lt;/li&gt;
&lt;li&gt;You need to define a &lt;code&gt;tuple&lt;/code&gt; type.&lt;/li&gt;
&lt;li&gt;You need to create a &lt;code&gt;type alias&lt;/code&gt; that cannot be represented by an &lt;code&gt;interface&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;You need to define a type that represents a complex data structure.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="use-interfaces-when"&gt;
&lt;a href="#use-interfaces-when" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Use &lt;code&gt;interfaces&lt;/code&gt; when:
&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;You need to define the shape of an object.&lt;/li&gt;
&lt;li&gt;You need to enforce &lt;strong&gt;contracts&lt;/strong&gt; in your code.&lt;/li&gt;
&lt;li&gt;You need to extend or implement &lt;code&gt;interfaces&lt;/code&gt; in your code.&lt;/li&gt;
&lt;li&gt;You need to define &lt;code&gt;optional&lt;/code&gt; properties or &lt;code&gt;readonly&lt;/code&gt; properties.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In general, &lt;em&gt;types are more flexible than interfaces&lt;/em&gt;, but &lt;em&gt;interfaces are more specific&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Types can represent more complex data structures and allow for more advanced type manipulation.
Interfaces, on the other hand, are more rigid and allow for stricter type checking and code contracts.&lt;/p&gt;</description></item><item><title>Credibility - The Secret Sauce for Success at Work</title><link>https://www.yopa.page/blog/2023-04-07-credibility-the-secret-sauce-for-success-at-work.html</link><pubDate>Fri, 07 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-07-credibility-the-secret-sauce-for-success-at-work.html</guid><description>
&lt;p&gt;So, I decided to hit the reset button on my career and start from scratch. Was it worth it? Honestly, I&amp;rsquo;m still figuring it out. But one thing&amp;rsquo;s for sure, if I had these things done on my first career path, things would&amp;rsquo;ve turned out way differently.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s dive in.&lt;/p&gt;
&lt;h2 id="do-more-than-just-meet-expectations"&gt;
&lt;a href="#do-more-than-just-meet-expectations" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Do More Than Just Meet Expectations
&lt;/h2&gt;
&lt;p&gt;Sure, completing assigned tasks and meeting deadlines is important, but that&amp;rsquo;s just the bare minimum. To build credibility, &lt;strong&gt;you need to go above and beyond what&amp;rsquo;s expected&lt;/strong&gt;. Identify unmet needs and do work without being asked. It&amp;rsquo;s like being the superhero of the office, swooping in to save the day and impressing everyone with your proactive attitude.&lt;/p&gt;
&lt;h2 id="dont-be-a-one-trick-pony"&gt;
&lt;a href="#dont-be-a-one-trick-pony" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Don&amp;rsquo;t Be A One-Trick Pony
&lt;/h2&gt;
&lt;p&gt;Being inflexible and &lt;strong&gt;saying &amp;ldquo;it&amp;rsquo;s not my job&amp;rdquo; is like shooting yourself in the foot.&lt;/strong&gt; You&amp;rsquo;re limiting your growth and opportunities by only accepting certain kinds of work. Instead, be open to taking on tasks outside of your job profile. It&amp;rsquo;s like being a jack-of-all-trades and a master of credibility.&lt;/p&gt;
&lt;h2 id="plan-like-a-pro"&gt;
&lt;a href="#plan-like-a-pro" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Plan Like A Pro
&lt;/h2&gt;
&lt;p&gt;Proactively identifying problems and creating a plan to address them is like having a superpower. Instead of reacting to problems as they arise, &lt;strong&gt;use the premortem technique to visualize what might go wrong and create a plan to solve those issues.&lt;/strong&gt; It&amp;rsquo;s like being a master strategist and a hero of problem-solving.&lt;/p&gt;
&lt;h2 id="be-the-solution-not-the-problem"&gt;
&lt;a href="#be-the-solution-not-the-problem" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Be The Solution, Not The Problem
&lt;/h2&gt;
&lt;p&gt;Don&amp;rsquo;t be the Debbie Downer who only points out problems. &lt;strong&gt;Be the solutionist who comes prepared with ideas and solutions.&lt;/strong&gt; It&amp;rsquo;s like being a superhero who always saves the day and impresses everyone with their problem-solving skills.&lt;/p&gt;
&lt;h2 id="be-the-team-player"&gt;
&lt;a href="#be-the-team-player" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Be The Team Player
&lt;/h2&gt;
&lt;p&gt;Prioritizing the team over personal interests is like being the MVP of the office. Employees who do work that matters to the organization and contribute to the team&amp;rsquo;s success build credibility while also contributing to their own learning and growth. It&amp;rsquo;s like being a team player and a superhero at the same time.&lt;/p&gt;
&lt;h2 id="share-your-superpowers"&gt;
&lt;a href="#share-your-superpowers" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Share Your Superpowers
&lt;/h2&gt;
&lt;p&gt;Sharing knowledge is like spreading your superpowers around the office. Instead of withholding knowledge, share it with others and help them grow. By doing so, you become a valued resource to your colleagues, managers, and stakeholders. It&amp;rsquo;s like being a mentor and a superhero all in one.&lt;/p&gt;
&lt;h2 id="adapt-to-the-future"&gt;
&lt;a href="#adapt-to-the-future" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Adapt To The Future
&lt;/h2&gt;
&lt;p&gt;Being willing to change and adapt to future needs is like being a superhero who&amp;rsquo;s always ready for the next challenge. Instead of sticking to old methods, &lt;strong&gt;expand your comfort zone and develop new skills.&lt;/strong&gt; It&amp;rsquo;s like being a chameleon and a superhero at the same time.&lt;/p&gt;
&lt;h2 id="respect-the-clock"&gt;
&lt;a href="#respect-the-clock" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Respect The Clock
&lt;/h2&gt;
&lt;p&gt;Showing up on time is like being a superhero who&amp;rsquo;s always punctual and respectful of others&amp;rsquo; time. Don&amp;rsquo;t be the one who&amp;rsquo;s always late and keeps others waiting. &lt;strong&gt;Respect the clock and build some credibility.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;May your career be bug-free.
Cheer! 🍺&lt;/p&gt;</description></item><item><title>Optional Chaining in TypeScript</title><link>https://www.yopa.page/blog/2023-04-06-optional-chaining-in-typescript.html</link><pubDate>Thu, 06 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-06-optional-chaining-in-typescript.html</guid><description>
&lt;h2 id="what-is-optional-chaining"&gt;
&lt;a href="#what-is-optional-chaining" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What is Optional Chaining?
&lt;/h2&gt;
&lt;p&gt;Optional chaining is a syntax feature that allows you to access deeply nested properties within an object without having to check for the existence of each intermediate property. With optional chaining, if any property in the chain is null or undefined, the entire chain evaluates to undefined without throwing an error.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s consider a simple example. Suppose we have an object &lt;code&gt;user&lt;/code&gt; with a nested property &lt;code&gt;address.street&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Address&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;street&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;city&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;country&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;age&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;address?&lt;/span&gt;: &lt;span class="kt"&gt;Address&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;: &lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;age&lt;/span&gt;: &lt;span class="kt"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Without optional chaining, you would need to check for the existence of the &lt;code&gt;address&lt;/code&gt; property before accessing the &lt;code&gt;street&lt;/code&gt; property:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;street&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;user.address.street&lt;/span&gt; : &lt;span class="kt"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;With optional chaining&lt;/strong&gt;, you can access the street property more concisely:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;street&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;address&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;street&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;If the &lt;code&gt;address&lt;/code&gt; property is null or undefined, the entire expression evaluates to undefined without throwing an error.&lt;/p&gt;
&lt;h2 id="optional-chaining-with-function-calls"&gt;
&lt;a href="#optional-chaining-with-function-calls" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Optional Chaining with Function Calls
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;age&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;greet&lt;/span&gt;&lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;: &lt;span class="kt"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;age&lt;/span&gt;: &lt;span class="kt"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You can use optional chaining to call the &lt;code&gt;greet&lt;/code&gt; method only if it exists:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;greeting&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;greet&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="you-can-even-use-this-with-array-indices"&gt;
&lt;a href="#you-can-even-use-this-with-array-indices" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
You can even use this with Array Indices!
&lt;/h2&gt;
&lt;p&gt;Consider an example where you have an array of users, and you want to access the first user&amp;rsquo;s name:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;: &lt;span class="kt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;age&lt;/span&gt;: &lt;span class="kt"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Bob&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;age&lt;/span&gt;: &lt;span class="kt"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;firstName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, the optional chaining operator is used twice: once to check if the &lt;code&gt;users&lt;/code&gt; array exists, and once to check if the first element in the array exists. If either the &lt;code&gt;users&lt;/code&gt; array or the first element is null or undefined, the expression evaluates to undefined without throwing an error.&lt;/p&gt;</description></item><item><title>Using TypeScript's Partial Utility Type</title><link>https://www.yopa.page/blog/2023-04-05-trick-to-write-better-typescript-5.html</link><pubDate>Wed, 05 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-05-trick-to-write-better-typescript-5.html</guid><description>
&lt;h2 id="partial-utility-type"&gt;
&lt;a href="#partial-utility-type" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Partial Utility Type
&lt;/h2&gt;
&lt;p&gt;The Partial utility type is a predefined TypeScript utility that makes all properties of a given type optional. This can be useful when you need to create a type that allows for partial updates or when you want to provide default values for missing properties.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s start with an example. Consider the following &lt;code&gt;User&lt;/code&gt; type:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Copy&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;id&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;email&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;age&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now, suppose we want to create a function to update a user&amp;rsquo;s information. The function should accept an object with optional properties, allowing you to update only specific properties. The &lt;strong&gt;Partial&lt;/strong&gt; utility type can help achieve this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Copy&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PartialUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Partial&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;PartialUser&lt;/code&gt; will now have the same properties as &lt;code&gt;User&lt;/code&gt;, but all of them will be &lt;strong&gt;optional&lt;/strong&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;Copy&lt;/span&gt; &lt;span class="nx"&gt;code&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;updateUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;updates&lt;/span&gt;: &lt;span class="kt"&gt;PartialUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// Update the user with the provided information
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Usage example:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;updateUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;updateUser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;bob@example.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;age&lt;/span&gt;: &lt;span class="kt"&gt;25&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;</description></item><item><title>Partial Application of Generic Types in TypeScript</title><link>https://www.yopa.page/blog/2023-04-04-trick-to-write-better-typescript-4.html</link><pubDate>Tue, 04 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-04-trick-to-write-better-typescript-4.html</guid><description>
&lt;p&gt;Another cool trick in TypeScript is to use &lt;strong&gt;Partial Application of Generic Types&lt;/strong&gt;. This feature enables you to create a new type by applying some of the type parameters of a generic type. This can help you to create reusable and modular types that can be combined in various ways.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s take a look at an example:&lt;/p&gt;
&lt;p&gt;Suppose you have a generic type called &lt;code&gt;ApiResponse&lt;/code&gt; which represents the response from an API. This type has two type parameters, &lt;code&gt;Data&lt;/code&gt; and &lt;code&gt;Meta&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ApiResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;data&lt;/span&gt;: &lt;span class="kt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;meta&lt;/span&gt;: &lt;span class="kt"&gt;Meta&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now, you want to create a reusable &lt;code&gt;PaginatedResponse&lt;/code&gt; type that partially applies the &lt;code&gt;ApiResponse&lt;/code&gt; type with a fixed &lt;code&gt;meta&lt;/code&gt; property. You can achieve this using a generic type with a single type parameter:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;PaginationMeta&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;currentPage&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;totalPages&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;itemsPerPage&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PaginatedResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ApiResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;Data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;PaginationMeta&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, we first define an interface called &lt;code&gt;PaginationMeta&lt;/code&gt; that represents the metadata for a paginated response. Next, we create a new type &lt;code&gt;PaginatedResponse&lt;/code&gt; that takes a single type parameter Data and partially applies the &lt;code&gt;ApiResponse&lt;/code&gt; type by fixing the &lt;code&gt;Meta&lt;/code&gt; type parameter to &lt;code&gt;PaginationMeta&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Now, you can use the &lt;code&gt;PaginatedResponse&lt;/code&gt; type with different data types:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;id&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;PaginatedUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;PaginatedResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;User&lt;/span&gt;&lt;span class="err"&gt;[]&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;users&lt;/span&gt;: &lt;span class="kt"&gt;PaginatedUsers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;: &lt;span class="kt"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;: &lt;span class="kt"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Bob&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;],&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;meta&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;currentPage&lt;/span&gt;: &lt;span class="kt"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;totalPages&lt;/span&gt;: &lt;span class="kt"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;itemsPerPage&lt;/span&gt;: &lt;span class="kt"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, we define a User type and then create a &lt;code&gt;PaginatedUsers&lt;/code&gt; type that represents a paginated response containing an array of users. The &lt;code&gt;PaginatedResponse&lt;/code&gt; type simplifies creating paginated responses for different data types without having to redefine the metadata properties for each type.&lt;/p&gt;
&lt;p&gt;Partial Application of Generic Types can help you create more versatile and maintainable TypeScript code by allowing you to create reusable types that can be combined in various ways. This can reduce duplication and make your code more modular and easier to work with.&lt;/p&gt;</description></item><item><title>Modeling States with TypeScript Discriminated Unions</title><link>https://www.yopa.page/blog/2023-04-03-trick-to-write-better-typescript-3.html</link><pubDate>Mon, 03 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-03-trick-to-write-better-typescript-3.html</guid><description>
&lt;p&gt;Another cool trick in TypeScript is to use &lt;strong&gt;Discriminated Unions&lt;/strong&gt;.
Discriminated unions are a way to create a type that is a union of multiple types, where each type has a unique property (called a &amp;ldquo;&lt;strong&gt;discriminant&lt;/strong&gt;&amp;rdquo;) that can be used to distinguish between them. This makes it easier to handle different types in a type-safe manner.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s take a look at an example:&lt;/p&gt;
&lt;p&gt;Suppose you are building a messaging app that supports different types of messages: text, image, and video. You can create separate types for each message type and use a discriminant property called &lt;code&gt;type&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;TextMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;content&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ImageMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;image&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;url&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;width&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;height&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;VideoMessage&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;video&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;url&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;duration&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now, you can create a &lt;code&gt;Message&lt;/code&gt; type as a discriminated union of these message types:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Message&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;TextMessage&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;ImageMessage&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;VideoMessage&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This &lt;code&gt;Message&lt;/code&gt; type can represent any of the three message types. You can handle different message types using a type-safe switch statement based on the &lt;code&gt;type&lt;/code&gt; property:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;handleMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;: &lt;span class="kt"&gt;Message&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Text message: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;image&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sb"&gt;`Image message: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; (&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;x&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;)`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;video&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="sb"&gt;`Video message: &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; (&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;duration&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; seconds)`&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;TypeScript will infer the correct type within each case block, ensuring that you are using the correct properties for each message type.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s how you can use the &lt;code&gt;handleMessage&lt;/code&gt; function with different message types:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;textMsg&lt;/span&gt;: &lt;span class="kt"&gt;TextMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;text&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Hello, World!&amp;#34;&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;imageMsg&lt;/span&gt;: &lt;span class="kt"&gt;ImageMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;image&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://example.com/image.jpg&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;width&lt;/span&gt;: &lt;span class="kt"&gt;800&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;height&lt;/span&gt;: &lt;span class="kt"&gt;600&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;videoMsg&lt;/span&gt;: &lt;span class="kt"&gt;VideoMessage&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;type&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;video&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;https://example.com/video.mp4&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;duration&lt;/span&gt;: &lt;span class="kt"&gt;120&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;handleMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;textMsg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;handleMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;imageMsg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;handleMessage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;videoMsg&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;Discriminated Unions&lt;/strong&gt; help you create more versatile and maintainable TypeScript code by allowing you to work with different types in a type-safe manner. This can help you catch errors early in the development process and make your code easier to understand and modify.&lt;/p&gt;</description></item><item><title>Using Conditional Types in TypeScript</title><link>https://www.yopa.page/blog/2023-04-02-trick-to-write-better-typescript-2.html</link><pubDate>Sun, 02 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-02-trick-to-write-better-typescript-2.html</guid><description>
&lt;p&gt;Another cool trick in TypeScript is to use conditional types. Conditional types enable you to create new types by conditionally selecting different types based on the input type. This can help you make your code more flexible and adaptable.&lt;/p&gt;
&lt;p&gt;Here&amp;rsquo;s an example using conditional types:&lt;/p&gt;
&lt;p&gt;Suppose you have a type &lt;code&gt;Response&lt;/code&gt; that can have either an &lt;code&gt;error&lt;/code&gt; or a &lt;code&gt;data&lt;/code&gt; property:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;error?&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;data?&lt;/span&gt;: &lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You want to create a utility type that extracts the &lt;code&gt;data&lt;/code&gt; type if it exists, or returns &lt;code&gt;never&lt;/code&gt; if it doesn&amp;rsquo;t.
You can use a conditional type to achieve this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ExtractDataType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt; &lt;span class="kr"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;infer&lt;/span&gt; &lt;span class="na"&gt;U&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;U&lt;/span&gt; : &lt;span class="kt"&gt;never&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;details&gt;
&lt;summary&gt;More detailed explanation&lt;/summary&gt;
Here, `ExtractDataType` is a generic type that takes a type parameter `T`. It checks if `T` extends the `Response` type. If it does, the type `U` is inferred from the `Response&lt;U&gt;` type and returned. If it doesn't, the `never` type is returned.
&lt;/details&gt;
&lt;p&gt;Now, you can use &lt;code&gt;ExtractDataType&lt;/code&gt; to get the type of the &lt;code&gt;data&lt;/code&gt; property from a &lt;code&gt;Response&lt;/code&gt; type:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;NumberResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ExtractedNumber&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ExtractDataType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;NumberResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt; &lt;span class="c1"&gt;// This will be &amp;#39;number&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;StringResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ExtractedString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ExtractDataType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;StringResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt; &lt;span class="c1"&gt;// This will be &amp;#39;string&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You can also use this utility type with more complex types:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;id&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;UserResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ExtractedUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;ExtractDataType&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;UserResponse&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt; &lt;span class="c1"&gt;// This will be &amp;#39;User&amp;#39;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;ExtractDataType&lt;/code&gt; utility type is a good example of a conditional type that simplifies working with complex types, making the code more maintainable and less error-prone.&lt;/p&gt;</description></item><item><title>Top Techniques for Efficient Array Shuffling</title><link>https://www.yopa.page/blog/2023-04-01-best-way-to-shuffle-an-array.html</link><pubDate>Sat, 01 Apr 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-04-01-best-way-to-shuffle-an-array.html</guid><description>
&lt;h1 id="best-way-to-shuffle-an-array-the-fisher-yates-shuffle-algorithm"&gt;
&lt;a href="#best-way-to-shuffle-an-array-the-fisher-yates-shuffle-algorithm" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Best Way to Shuffle an Array: The Fisher-Yates Shuffle Algorithm
&lt;/h1&gt;
&lt;p&gt;Fisher–Yates produces an unbiased shuffle when its random-number source is uniform. It runs in linear time and can be implemented without mutating the input array.&lt;/p&gt;
&lt;h2 id="what-is-the-fisher-yates-shuffle-algorithm"&gt;
&lt;a href="#what-is-the-fisher-yates-shuffle-algorithm" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What is the Fisher-Yates Shuffle Algorithm?
&lt;/h2&gt;
&lt;p&gt;The algorithm walks backward through the array and swaps each element with a uniformly selected element at or before its current position.&lt;/p&gt;
&lt;h2 id="how-does-it-work"&gt;
&lt;a href="#how-does-it-work" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
How does it work?
&lt;/h2&gt;
&lt;p&gt;To use the Fisher-Yates shuffle algorithm, follow these simple steps:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Start at the last element in the array.&lt;/li&gt;
&lt;li&gt;Generate a random number between 0 and the current index.&lt;/li&gt;
&lt;li&gt;Swap the current element with the one at the randomly generated index.&lt;/li&gt;
&lt;li&gt;Move backwards one index in the array and repeat steps 2-3 until you reach the first element.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;And just like that, your array is now jumbled up and ready for action! There&amp;rsquo;s no telling what order the elements will be in now, so get ready for some surprises.&lt;/p&gt;
&lt;h2 id="why-should-i-use-the-fisher-yates-shuffle-algorithm"&gt;
&lt;a href="#why-should-i-use-the-fisher-yates-shuffle-algorithm" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why should I use the Fisher-Yates Shuffle Algorithm?
&lt;/h2&gt;
&lt;p&gt;Not only is the Fisher-Yates shuffle algorithm a fun and unpredictable way to shuffle your arrays, but it&amp;rsquo;s also mathematically sound. Each element in the array has an equal chance of ending up in any position, so you don&amp;rsquo;t have to worry about any pesky biases or patterns.&lt;/p&gt;
&lt;p&gt;Do not use &lt;code&gt;array.sort(() =&amp;gt; Math.random() - 0.5)&lt;/code&gt;, which does not produce a reliable uniform shuffle. The example below uses &lt;code&gt;Math.random&lt;/code&gt;, so it is suitable for ordinary UI behavior and simulations, not cryptographic selection, gambling, or security tokens. Those cases require a cryptographically secure random source and domain-specific review.&lt;/p&gt;
&lt;h2 id="example"&gt;
&lt;a href="#example" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Example
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;shuffleArray&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;: &lt;span class="kt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shuffledArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;array&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;shuffledArray&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;shuffledArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;shuffledArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;shuffledArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nx"&gt;shuffledArray&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;shuffledArray&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;shuffledArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;shuffleArray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myArray&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shuffledArray&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="c1"&gt;// [2, 1, 4, 3, 5]
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;details&gt;
&lt;summary&gt;Explaining the Example&lt;/summary&gt;
&lt;p&gt;In this example, we have defined a function called shuffleArray that takes an array of any type and returns a new shuffled array.&lt;/p&gt;
&lt;p&gt;We first create a copy of the input array using the spread operator (&amp;hellip;) and assign it to the variable shuffledArray. This ensures that the original array remains unchanged.&lt;/p&gt;
&lt;p&gt;Next, we iterate over the elements of the shuffledArray in reverse order using a for loop. For each iteration, we generate a random index j between 0 and the current index i using the formula Math.floor(Math.random() * (i + 1)).&lt;/p&gt;
&lt;p&gt;We then swap the element at index i with the element at index j using destructuring assignment, which avoids the need for a temporary variable. By the end of the loop, all the elements of the shuffledArray will have been swapped around randomly, resulting in a shuffled array.&lt;/p&gt;
&lt;p&gt;Finally, we return the shuffledArray from the function, and log it to the console to verify that it has been shuffled properly.&lt;/p&gt;
&lt;/details&gt;
&lt;h2 id="get-shufflin"&gt;
&lt;a href="#get-shufflin" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Get Shufflin'!
&lt;/h2&gt;
&lt;p&gt;Fisher–Yates is a sound default for ordinary array shuffling. Select the randomness source according to the risk of the application.&lt;/p&gt;</description></item><item><title>Using Mapped Types in TypeScript</title><link>https://www.yopa.page/blog/2023-03-31-trick-to-write-better-typescript-1.html</link><pubDate>Fri, 31 Mar 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-03-31-trick-to-write-better-typescript-1.html</guid><description>
&lt;p&gt;A cool trick to write better TypeScript code is to use &lt;strong&gt;mapped types&lt;/strong&gt;.&lt;/p&gt;
&lt;h2 id="what-is-mapped-types"&gt;
&lt;a href="#what-is-mapped-types" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What is Mapped types
&lt;/h2&gt;
&lt;p&gt;Mapped types allow you to create new types by transforming properties of existing types, making your code more flexible, and less prone to errors. Let&amp;rsquo;s take a look at an example:&lt;/p&gt;
&lt;p&gt;Suppose you have a type representing a User:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;id&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;email&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;age&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now, you want to create a new type with optional properties based on the &lt;code&gt;User&lt;/code&gt; type. You can use a mapped type to achieve this:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;P&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;OptionalUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;code&gt;OptionalUser&lt;/code&gt; will now have the same properties as &lt;code&gt;User&lt;/code&gt;, but all of them will be optional:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user1&lt;/span&gt;: &lt;span class="kt"&gt;OptionalUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;id&lt;/span&gt;: &lt;span class="kt"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Alice&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user2&lt;/span&gt;: &lt;span class="kt"&gt;OptionalUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;id&lt;/span&gt;: &lt;span class="kt"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;bob@example.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;You can even go further and create a utility type to make any property of a given type readonly:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Readonly&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;T&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;readonly&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;P&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="k"&gt;keyof&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;T&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;P&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;ReadonlyUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Readonly&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now you have a ReadonlyUser type where all properties are immutable:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;span class="lnt"&gt;9
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;user3&lt;/span&gt;: &lt;span class="kt"&gt;ReadonlyUser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;id&lt;/span&gt;: &lt;span class="kt"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Carol&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;carol@example.com&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;age&lt;/span&gt;: &lt;span class="kt"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// This will produce a TypeScript error, as the name property is readonly:
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;user3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Catherine&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Mapped types can help you create more versatile and maintainable TypeScript code by reducing duplication and allowing for easy transformation of existing types.&lt;/p&gt;
&lt;p&gt;for bringing this to my attention.)&lt;/p&gt;</description></item><item><title>TypeScript Unions - Exploring Discriminated Unions and Best Practices</title><link>https://www.yopa.page/blog/2023-03-30-unions-in-typescript.html</link><pubDate>Thu, 30 Mar 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-03-30-unions-in-typescript.html</guid><description>
&lt;h2 id="why-do-you-need-unions-in-typescript"&gt;
&lt;a href="#why-do-you-need-unions-in-typescript" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why do you need unions in TypeScript
&lt;/h2&gt;
&lt;p&gt;Well, let&amp;rsquo;s say you have a function that can receive different types of arguments, but you don&amp;rsquo;t want to write a different function for each type. You can use a union to define that the function can receive one type or another, or even a combination of them.&lt;/p&gt;
&lt;p&gt;For example, imagine you have a function that calculates the area of a shape. You could define a union type that represents the different shapes that your function can handle:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;span class="lnt"&gt;33
&lt;/span&gt;&lt;span class="lnt"&gt;34
&lt;/span&gt;&lt;span class="lnt"&gt;35
&lt;/span&gt;&lt;span class="lnt"&gt;36
&lt;/span&gt;&lt;span class="lnt"&gt;37
&lt;/span&gt;&lt;span class="lnt"&gt;38
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Shape&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Square&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Circle&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="nx"&gt;Rectangle&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Square&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;square&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;size&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Circle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;circle&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;radius&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;type&lt;/span&gt; &lt;span class="nx"&gt;Rectangle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rectangle&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;width&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;height&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;calculateArea&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shape&lt;/span&gt;: &lt;span class="kt"&gt;Shape&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;square&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;size&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;circle&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PI&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;radius&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rectangle&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;width&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;shape&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;height&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Invalid shape&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;square&lt;/span&gt;: &lt;span class="kt"&gt;Square&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;square&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;size&lt;/span&gt;: &lt;span class="kt"&gt;5&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;circle&lt;/span&gt;: &lt;span class="kt"&gt;Circle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;circle&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;radius&lt;/span&gt;: &lt;span class="kt"&gt;3&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rectangle&lt;/span&gt;: &lt;span class="kt"&gt;Rectangle&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;kind&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;rectangle&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;width&lt;/span&gt;: &lt;span class="kt"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;height&lt;/span&gt;: &lt;span class="kt"&gt;2&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calculateArea&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;square&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Output: 25
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calculateArea&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;circle&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Output: 28.274333882308138
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;calculateArea&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;rectangle&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="c1"&gt;// Output: 8
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In the example above, we define a union type &lt;code&gt;Shape&lt;/code&gt; that can represent a &lt;code&gt;Square&lt;/code&gt;, a &lt;code&gt;Circle&lt;/code&gt;, or a &lt;code&gt;Rectangle&lt;/code&gt;. Each type has its own properties, but they all share a common property kind that helps us determine which shape we are dealing with inside the &lt;code&gt;calculateArea&lt;/code&gt; function.&lt;/p&gt;
&lt;p&gt;We can also see that the function can handle different types of shapes thanks to the union type, and we don&amp;rsquo;t need to write a different function for each shape.&lt;/p&gt;
&lt;h2 id="what-is-the-best-practice-when-using-unions-in-typescript"&gt;
&lt;a href="#what-is-the-best-practice-when-using-unions-in-typescript" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What is the best practice when using unions in TypeScript?
&lt;/h2&gt;
&lt;p&gt;One best practice is to use discriminants, like the kind property in the example above, to help you determine which type you are dealing with inside your functions. This way, you can avoid runtime errors by ensuring that you are only using the properties that are common to all types in the union.&lt;/p&gt;
&lt;p&gt;Another best practice is to keep your union types as simple as possible. Unions can quickly become complex if you start nesting them or adding too many types, which can make your code harder to read and maintain.&lt;/p&gt;</description></item><item><title>Understanding Taxonomy - Definition, Importance, and Applications</title><link>https://www.yopa.page/blog/2023-03-30-taxonomy.html</link><pubDate>Thu, 30 Mar 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-03-30-taxonomy.html</guid><description>
&lt;h2 id="what-chatgpt-thinks-about-taxonomy"&gt;
&lt;a href="#what-chatgpt-thinks-about-taxonomy" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What ChatGPT thinks about Taxonomy
&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&amp;ldquo;Taxonomy is the buzzword that makes software engineers feel like they have their code organized, even if it&amp;rsquo;s just a bunch of fancy categories with no real purpose.&amp;rdquo;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;IMPRESSIVE!&lt;/strong&gt;
Although ChatGPT may be correct in its meaning, it&amp;rsquo;s still important to understand what it&amp;rsquo;s all about because let&amp;rsquo;s be real - big boys love their fancy jargon.&lt;/p&gt;
&lt;h2 id="what-is-taxonomy"&gt;
&lt;a href="#what-is-taxonomy" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What is Taxonomy?
&lt;/h2&gt;
&lt;p&gt;Before we dive into the software aspect of taxonomy, let&amp;rsquo;s first define what it means.
Taxonomy is the process of organizing and classifying things based on their characteristics.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;In biology, this means grouping organisms based on their physical traits and evolutionary history.&lt;/li&gt;
&lt;li&gt;In software engineering, it means categorizing different types of code based on their functionality and purpose.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="the-taxonomy-of-programming-languages"&gt;
&lt;a href="#the-taxonomy-of-programming-languages" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Taxonomy of Programming Languages
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s start with an example that most people are familiar with: programming languages. There are so many programming languages out there that it can be overwhelming to choose which one to use for a specific project. That&amp;rsquo;s where taxonomy comes in handy. We can group programming languages based on their characteristics, such as syntax, purpose, and popularity.&lt;/p&gt;
&lt;p&gt;For instance, we could classify programming languages into the following categories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Web Development Languages&lt;/strong&gt;: HTML, CSS, JavaScript, PHP, Ruby, Python&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data Science Languages&lt;/strong&gt;: R, Python, Julia, MATLAB&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;System Administration Languages&lt;/strong&gt;: Bash, PowerShell, Perl&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mobile App Development Languages&lt;/strong&gt;: Java, Swift, Kotlin&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;See? It&amp;rsquo;s not so scary after all. By organizing programming languages into these categories, we can easily determine which language is best suited for a particular task.&lt;/p&gt;
&lt;h2 id="the-taxonomy-of-software-design-patterns"&gt;
&lt;a href="#the-taxonomy-of-software-design-patterns" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Taxonomy of Software Design Patterns
&lt;/h2&gt;
&lt;p&gt;Another area where taxonomy is useful in software engineering is in software design patterns. Design patterns are reusable solutions to common software problems. There are many different types of design patterns, each with its own unique characteristics and use cases.&lt;/p&gt;
&lt;p&gt;For example, we could classify design patterns into the following categories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Singleton Pattern&lt;/strong&gt;: Creational Pattern&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Observer Pattern&lt;/strong&gt;: Behavioral Pattern&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adapter Pattern&lt;/strong&gt;: Structural Pattern&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By organizing design patterns into categories like creational, behavioral, and structural, we can easily identify which pattern is best suited for a particular problem.&lt;/p&gt;
&lt;h2 id="this-can-be-totally-different-in-your-company-though"&gt;
&lt;a href="#this-can-be-totally-different-in-your-company-though" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
This can be totally different in your company though!
&lt;/h2&gt;
&lt;p&gt;For instance, in my company, Taxonomies are used to identify and track individual elements of configuration across different environments and versions, as well as to associate configuration elements with product taxonomies. They also help with version control and reverting erroneous configuration changes. Additionally, taxonomies aid in migrating configurations between deprecated and replacement taxonomies, and enable no-touch configuration deployment and feature version migration in production environments.&lt;/p&gt;
&lt;h2 id="but-i-just-saw-you-use-product-taxonomies-in-your-blog-post-what-is-that"&gt;
&lt;a href="#but-i-just-saw-you-use-product-taxonomies-in-your-blog-post-what-is-that" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
But I just saw you use product taxonomies in your blog post! What is that?
&lt;/h2&gt;
&lt;p&gt;Well, imagine if your software system was a big zoo.
Just like how animals are categorized into different species based on their characteristics and traits, software products or components can be organized and classified into different categories based on their functionality and purpose. This is where product taxonomy comes in handy.&lt;/p&gt;
&lt;p&gt;Think of product taxonomy as the zookeeper who makes sure that all the animals are in the right enclosures.
In software engineering, product taxonomy helps developers organize and classify software products or components into different categories, making it easier to understand and navigate the software system.&lt;/p&gt;
&lt;p&gt;For instance, let&amp;rsquo;s say you&amp;rsquo;re developing a mobile app. You could use product taxonomy to categorize the different features and functions of the app. You might have categories like:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;User Interface: login screen, home screen, settings&lt;/li&gt;
&lt;li&gt;Functionality: messaging, notifications, search&lt;/li&gt;
&lt;li&gt;Integration: social media, payment gateway, third-party APIs&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;By using product taxonomy, you can better understand the relationships between different software products or components and make more informed decisions about how to design, develop, and implement them.&lt;/p&gt;
&lt;p&gt;P.S. Wow, we just waded through a swamp of jargon and buzzwords. I wish we could just speak plainly in our field, but alas, some folks think just using fancy words makes them sound smarter even if they do not know what they are talking about. Personally, I think it&amp;rsquo;s pretty sad - like putting a top hat on a hamster and calling it a gentleman. Until next time. Cheer! 🍺&lt;/p&gt;</description></item><item><title>Efficient Dependency Injection for Testable Code</title><link>https://www.yopa.page/blog/2023-03-29-testingwithdi.html</link><pubDate>Wed, 29 Mar 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-03-29-testingwithdi.html</guid><description>
&lt;p&gt;Dependency injection makes a component&amp;rsquo;s collaborators explicit. A unit test can supply a small fake at that boundary, while separate integration tests verify the real adapter and external system.&lt;/p&gt;
&lt;h2 id="dependency-injection-and-test-boundaries"&gt;
&lt;a href="#dependency-injection-and-test-boundaries" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Dependency Injection and test boundaries
&lt;/h2&gt;
&lt;p&gt;Dependency Injection provides us with everything we need to make our code easier to test. Just like Batman&amp;rsquo;s utility belt, it equips us with tools we can use to fight the evils of untestable code.&lt;/p&gt;
&lt;h3 id="testing-example-1"&gt;
&lt;a href="#testing-example-1" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Testing example 1:
&lt;/h3&gt;
&lt;p&gt;For example, let&amp;rsquo;s say you have a class that needs to communicate with an external API. With Dependency Injection, you can pass a fake or mock API client to your class during testing, and voila! You can test your code without ever having to connect to an actual API.&lt;/p&gt;
&lt;h2 id="tired-of-being-bitten-by-integration-tests"&gt;
&lt;a href="#tired-of-being-bitten-by-integration-tests" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Tired of Being Bitten by Integration Tests?
&lt;/h2&gt;
&lt;p&gt;Integration tests are slow, cumbersome, and often just a big old mess. But Dependency Injection makes integration testing much more comfortable, like getting a hug from your dog.
By using DI to provide dependencies to your application, you can isolate different parts of your codebase better. This means &lt;strong&gt;you can write integration tests that only test how these isolated components work together&lt;/strong&gt;, without having to worry about everything else in your system.&lt;/p&gt;
&lt;h3 id="testing-example-2"&gt;
&lt;a href="#testing-example-2" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Testing example 2:
&lt;/h3&gt;
&lt;p&gt;For example, let&amp;rsquo;s say you have a class that relies on a database connection. By injecting the database connection via DI, you can write tests that only focus on that class&amp;rsquo;s behavior rather than worrying about the entire database setup.&lt;/p&gt;
&lt;h2 id="best-practices---because-nobody-deserves-sloppy-code"&gt;
&lt;a href="#best-practices---because-nobody-deserves-sloppy-code" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Best Practices - Because Nobody deserves Sloppy Code
&lt;/h2&gt;
&lt;h3 id="kiss-keep-it-simple-s"&gt;
&lt;a href="#kiss-keep-it-simple-s" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
K.I.S.S. (Keep It Simple S*****)
&lt;/h3&gt;
&lt;p&gt;Keep DI configuration small enough that the runtime object graph remains understandable.&lt;/p&gt;
&lt;p&gt;Inject only the dependencies the class actually uses. A growing constructor often signals that the class has accumulated too many responsibilities.&lt;/p&gt;
&lt;h2 id="a-real-world-example"&gt;
&lt;a href="#a-real-world-example" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
A Real-World Example
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s say you&amp;rsquo;re building an e-commerce website that needs to communicate with a payment gateway. Instead of hard-coding the payment gateway client into your checkout controller, you can use DI to pass in the client as a dependency.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;PaymentGateway&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amountInCents&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;transactionId&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;CheckoutService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="kr"&gt;readonly&lt;/span&gt; &lt;span class="nx"&gt;paymentGateway&lt;/span&gt;: &lt;span class="kt"&gt;PaymentGateway&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;processPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amountInCents&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentGateway&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;charge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amountInCents&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The unit test can inject a deterministic fake gateway. Keep at least one integration or contract test for the production gateway adapter; dependency injection improves isolation but does not make integration behavior unnecessary.&lt;/p&gt;</description></item><item><title>Understanding Inversion of Control (IoC) in Software Development - Definition and Benefits</title><link>https://www.yopa.page/blog/2023-03-28-inversionofcontrol.html</link><pubDate>Tue, 28 Mar 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-03-28-inversionofcontrol.html</guid><description>
&lt;h2 id="inversion-of-control-ioc"&gt;
&lt;a href="#inversion-of-control-ioc" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Inversion of Control (IoC)
&lt;/h2&gt;
&lt;p&gt;Inversion of Control (IoC) moves responsibility for orchestration or dependency creation outside the component doing the work.&lt;/p&gt;
&lt;h2 id="think-about-it-like-this"&gt;
&lt;a href="#think-about-it-like-this" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Think about it like this
&lt;/h2&gt;
&lt;p&gt;Normally, when you write code, you&amp;rsquo;re the one in control. You make all the decisions about what happens and when.
But with IoC, you&amp;rsquo;re basically saying &lt;strong&gt;&amp;ldquo;hey, I&amp;rsquo;m going to let someone else take the wheel for a bit&amp;rdquo;&lt;/strong&gt;.
And who&amp;rsquo;s that someone else? Your &lt;strong&gt;dependencies&lt;/strong&gt;!&lt;/p&gt;
&lt;h2 id="so-how-does-it-work"&gt;
&lt;a href="#so-how-does-it-work" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
So, how does it work?
&lt;/h2&gt;
&lt;p&gt;Well, let&amp;rsquo;s say you have a class that needs to use another class to do some work.
Normally, you&amp;rsquo;d create an instance of that class inside your original class and call its methods directly.
But with IoC, you&amp;rsquo;re going to let someone else handle that for you.
Maybe you&amp;rsquo;ll use a DI container or a framework to manage your dependencies, or maybe you&amp;rsquo;ll just pass them in as parameters.
(doesn&amp;rsquo;t it sound familiar? 🤔)&lt;/p&gt;
&lt;h2 id="example"&gt;
&lt;a href="#example" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Example
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s say you have a &lt;code&gt;Car&lt;/code&gt; class that needs an &lt;code&gt;Engine&lt;/code&gt; to run:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Car&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;engine&lt;/span&gt;: &lt;span class="kt"&gt;Engine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Engine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;start() {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;turnOn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, we&amp;rsquo;re creating a new instance of &lt;code&gt;Engine&lt;/code&gt; inside the &lt;code&gt;Car&lt;/code&gt; constructor.&lt;/p&gt;
&lt;p&gt;But with &lt;strong&gt;IoC&lt;/strong&gt;, we could do something like this instead:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Car&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;engine&lt;/span&gt;: &lt;span class="kt"&gt;Engine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;engine&lt;/span&gt;: &lt;span class="kt"&gt;Engine&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;start() {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;turnOn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myEngine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Engine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myCar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Car&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;myEngine&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;myCar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now, we&amp;rsquo;re passing the &lt;code&gt;Engine&lt;/code&gt; instance into the &lt;code&gt;Car&lt;/code&gt; constructor instead of creating it inside the class.
This gives us more control over how our dependencies are managed and makes our code more flexible.&lt;/p&gt;
&lt;h2 id="weaknesses"&gt;
&lt;a href="#weaknesses" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Weaknesses
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Complexity: IoC can be like a Rubik&amp;rsquo;s cube - fun to play with at first, but frustrating when you can&amp;rsquo;t figure out how to solve it. And unlike a Rubik&amp;rsquo;s cube, there&amp;rsquo;s no guidebook for IoC that tells you exactly what to do.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Performance: container startup, reflection, or a large dependency graph can add measurable overhead. Profile the real application before treating IoC as the cause.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Configuration: Setting up IoC can feel like trying to assemble IKEA furniture without instructions. You&amp;rsquo;re not sure which pieces go where, and by the time you&amp;rsquo;re done, you have a few extra screws left over.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Learning curve: IoC can be like learning a new language - at first, everything sounds like gibberish. But with practice, you start to understand the syntax and grammar, and before you know it, you&amp;rsquo;re speaking fluently.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;</description></item><item><title>Exploring Loose Coupling</title><link>https://www.yopa.page/blog/2023-03-21-loose-coupling.html</link><pubDate>Mon, 27 Mar 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-03-21-loose-coupling.html</guid><description>
&lt;blockquote&gt;
&lt;p&gt;“The advantage of loose coupling is the same in software design as it is in the physical socket and plug model:
Once the infrastructure is in place,
it can be used by anyone and adapted to changing needs and unforeseen requirements
without requiring large changes to the application code base and infrastructure.”&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;a href="https://www.amazon.com/Dependency-Injection-Principles-Practices-Patterns/dp/161729473X"&gt;Dependency Injection by Steven van Deursen&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Loose coupling&lt;/strong&gt; describes components that collaborate through small, stable contracts without depending on each other&amp;rsquo;s internal implementation. The goal is not to eliminate dependencies, but to make them explicit and replaceable.&lt;/p&gt;
&lt;h2 id="why-is-loose-coupling-important"&gt;
&lt;a href="#why-is-loose-coupling-important" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why is loose coupling important?
&lt;/h2&gt;
&lt;p&gt;For one thing, it makes your code easier to maintain. Because the different parts of your code aren&amp;rsquo;t tightly intertwined, you can make changes to one part without breaking everything else. It also makes your code more flexible. You can swap out one part for another without having to rewrite all of your code.
(Can you feel the sensation of déjà vu? I might have written about this topic in my blog before. Check it out and let me know!)&lt;/p&gt;
&lt;h2 id="how-do-you-achieve-loose-coupling"&gt;
&lt;a href="#how-do-you-achieve-loose-coupling" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
how do you achieve loose coupling?
&lt;/h2&gt;
&lt;p&gt;One approach is &lt;code&gt;dependency injection&lt;/code&gt;: provide a collaborator to a function or object instead of constructing it inside the consumer.&lt;/p&gt;
&lt;p&gt;Another approach is a small interface that describes the behavior the consumer needs without exposing implementation details.&lt;/p&gt;
&lt;p&gt;Loose coupling is useful when a dependency is likely to vary or when isolating it materially improves testing. Applying interfaces everywhere can add indirection without value, so start from a concrete change or testability need.&lt;/p&gt;</description></item><item><title>Injecting Properties Using Dependency Injection</title><link>https://www.yopa.page/blog/2023-03-27-property-injection-in-di.html</link><pubDate>Mon, 27 Mar 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-03-27-property-injection-in-di.html</guid><description>
&lt;h2 id="property-injection-in-di"&gt;
&lt;a href="#property-injection-in-di" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Property Injection in DI
&lt;/h2&gt;
&lt;p&gt;Property injection is a way of providing dependencies to a class by setting them as properties. It&amp;rsquo;s a simpler alternative to constructor injection, where dependencies are provided through the constructor.&lt;/p&gt;
&lt;h2 id="example"&gt;
&lt;a href="#example" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Example
&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Car&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;engine&lt;/span&gt;: &lt;span class="kt"&gt;Engine&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;start() {&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;engine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;turnOn&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;myCar&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Car&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;myCar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;engine&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Engine&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nx"&gt;myCar&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;start&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;In this example, we have a &lt;code&gt;Car&lt;/code&gt; class that has an &lt;code&gt;engine&lt;/code&gt; property. Instead of injecting the &lt;code&gt;engine&lt;/code&gt; dependency through the constructor, we&amp;rsquo;re setting it as a property after creating the &lt;code&gt;Car&lt;/code&gt; instance.&lt;/p&gt;
&lt;h2 id="weaknesses"&gt;
&lt;a href="#weaknesses" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Weaknesses
&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;One major issue is that it can lead to unexpected behavior if the dependencies are not set correctly. Imagine if we forget to set the &lt;code&gt;engine&lt;/code&gt; property before calling &lt;code&gt;start()&lt;/code&gt; - the car won&amp;rsquo;t start! &lt;strong&gt;This can be especially tricky to debug in larger codebases&lt;/strong&gt;.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Another weakness is that it can make testing more difficult. In order to test the Car class, we need to create an instance and set its engine property. This can be cumbersome, especially if there are multiple dependencies that need to be set.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="better-alternatives"&gt;
&lt;a href="#better-alternatives" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Better Alternatives
&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;One option is to use a DI container that can handle property injection for us.&lt;/li&gt;
&lt;li&gt;Another option is to use &lt;strong&gt;constructor injection instead&lt;/strong&gt;, which makes dependencies more explicit and easier to test.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>Understanding Method Injection in Dependency Injection - A Guide to Avoid Common Pitfalls</title><link>https://www.yopa.page/blog/2023-03-26-method-injection-in-dependency-injection.html</link><pubDate>Sun, 26 Mar 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-03-26-method-injection-in-dependency-injection.html</guid><description>
&lt;h2 id="method-injection-and-its-tradeoffs"&gt;
&lt;a href="#method-injection-and-its-tradeoffs" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Method injection and its tradeoffs
&lt;/h2&gt;
&lt;p&gt;Dependency injection (DI) is a powerful technique that allows you to write flexible, scalable code. One of its key features is method injection - the ability to pass dependencies to methods directly rather than through constructor injection.&lt;/p&gt;
&lt;p&gt;But beware! This feature can be dangerous if used incorrectly. Here&amp;rsquo;s what you need to know:&lt;/p&gt;
&lt;h2 id="so-what-is-the-problem-with-method-injection"&gt;
&lt;a href="#so-what-is-the-problem-with-method-injection" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
So What Is The Problem with Method Injection?
&lt;/h2&gt;
&lt;p&gt;Method injection can make your code more complex and harder to test. If you&amp;rsquo;re not careful, it can lead to tightly-coupled code that&amp;rsquo;s difficult to maintain.&lt;/p&gt;
&lt;p&gt;One common mistake is to use method injection excessively, resulting in &amp;ldquo;hidden&amp;rdquo; dependencies that are hard to track down. Always remember: just because you can inject a dependency into a method doesn&amp;rsquo;t mean you should.&lt;/p&gt;
&lt;p&gt;Second!
It can lead to unnecessary object creation. When you inject a dependency into a method, you may end up creating a new instance of that object every time the method is called. This can be particularly problematic for expensive or resource-intensive objects.&lt;/p&gt;
&lt;h2 id="what-is-a-better-way--constructor-injection"&gt;
&lt;a href="#what-is-a-better-way--constructor-injection" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
What Is A Better Way? ➡️ Constructor Injection
&lt;/h2&gt;
&lt;p&gt;Constructor injection is the usual default when a dependency is required for every valid instance.&lt;/p&gt;
&lt;p&gt;However, there are cases where method injection still makes sense. For example, if you have a single method that requires a specific dependency, it may be more appropriate to inject that dependency directly.&lt;/p&gt;
&lt;h2 id="example"&gt;
&lt;a href="#example" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Example
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s say we have a &lt;code&gt;UserService&lt;/code&gt; class that requires a &lt;code&gt;UserRepository&lt;/code&gt; dependency. Rather than injecting the repository through the constructor, we&amp;rsquo;ll use method injection to inject it directly into our &lt;code&gt;getUserById&lt;/code&gt; method:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;UserService&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;logger&lt;/span&gt;: &lt;span class="kt"&gt;ILogger&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="kr"&gt;async&lt;/span&gt; &lt;span class="nx"&gt;getUserById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt;: &lt;span class="kt"&gt;UserRepository&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Promise&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nt"&gt;User&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Getting user with ID &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getUserById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Notice that we also inject a logger dependency into the constructor using the more traditional constructor injection.&lt;/p&gt;</description></item><item><title>Understanding Constructor Injection in Dependency Injection - A Comprehensive Guide</title><link>https://www.yopa.page/blog/2023-03-25-construction-injection-in-dependency-injection.html</link><pubDate>Sat, 25 Mar 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-03-25-construction-injection-in-dependency-injection.html</guid><description>
&lt;h1 id="constructor-injection-in-dependency-injection"&gt;
&lt;a href="#constructor-injection-in-dependency-injection" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Constructor Injection in Dependency Injection
&lt;/h1&gt;
&lt;p&gt;Constructor injection supplies required dependencies when an object is created. It is a useful default because a successfully constructed instance has everything it needs to operate.&lt;/p&gt;
&lt;p&gt;Let&amp;rsquo;s say we have a simple example of a &lt;code&gt;Computer&lt;/code&gt; class that depends on another class, &lt;code&gt;CPU&lt;/code&gt;. We can use construction injection to provide the &lt;code&gt;CPU&lt;/code&gt; class to the &lt;code&gt;Computer&lt;/code&gt; constructor like so:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;CPU&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;brand&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;brand&lt;/span&gt;: &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;brand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;brand&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Computer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;public&lt;/span&gt; &lt;span class="nx"&gt;cpu&lt;/span&gt;: &lt;span class="kt"&gt;CPU&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cpu&lt;/span&gt;: &lt;span class="kt"&gt;CPU&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cpu&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;cpu&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;amd_cpu&lt;/span&gt;: &lt;span class="kt"&gt;CPU&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;CPU&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;AMD&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;computer&lt;/span&gt;: &lt;span class="kt"&gt;Computer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Computer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amd_cpu&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;With construction injection, our Computer class is no longer responsible for creating its own dependencies.
Instead, it&amp;rsquo;s provided with everything it needs to work correctly.&lt;/p&gt;</description></item><item><title>Applying the Open/Closed Principle</title><link>https://www.yopa.page/blog/2023-03-24-open-closed-principle-in-software-development.html</link><pubDate>Fri, 24 Mar 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-03-24-open-closed-principle-in-software-development.html</guid><description>
&lt;p&gt;The &lt;em&gt;Open/Closed Principle&lt;/em&gt; is an important principle used in software development. It falls under the &lt;strong&gt;SOLID&lt;/strong&gt; principles of Object-Oriented Programming (OOP). The OCP states that a software module should be open for extension but closed for modification. This means that you should be able to extend a module’s behavior without changing its source code.&lt;/p&gt;
&lt;h2 id="why-is-the-openclosed-principle-important"&gt;
&lt;a href="#why-is-the-openclosed-principle-important" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Why Is the Open/Closed Principle Important?
&lt;/h2&gt;
&lt;p&gt;The principle is useful where behavior changes along a known axis. A stable contract can let new implementations be added without repeatedly editing well-tested orchestration code. It does not mean source files must never change, and introducing an extension point before variation exists can create unnecessary complexity.&lt;/p&gt;
&lt;h2 id="example-1-payment-gateway"&gt;
&lt;a href="#example-1-payment-gateway" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Example 1: Payment Gateway
&lt;/h2&gt;
&lt;p&gt;Suppose a vending machine initially accepts coins and later needs card or mobile payments. If payment behavior is a genuine extension point, the machine&amp;rsquo;s orchestration can depend on a payment contract while each method supplies its own implementation.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;IPaymentMethod&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;pay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;CreditCard&lt;/span&gt; &lt;span class="kr"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;IPaymentMethod&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;pay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Paid &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; using Credit Card`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;PayPal&lt;/span&gt; &lt;span class="kr"&gt;implements&lt;/span&gt; &lt;span class="nx"&gt;IPaymentMethod&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;pay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`Paid &lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt; using PayPal`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;PaymentGateway&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;paymentMethods&lt;/span&gt;: &lt;span class="kt"&gt;IPaymentMethod&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;: &lt;span class="kt"&gt;IPaymentMethod&lt;/span&gt;&lt;span class="p"&gt;[])&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;processPayment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;paymentMethods&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;method&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;pay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amount&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;The &lt;code&gt;IPaymentMethod&lt;/code&gt; interface defines the behavior expected from each payment implementation. &lt;code&gt;CreditCard&lt;/code&gt; and &lt;code&gt;PayPal&lt;/code&gt; implement that contract.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;PaymentGateway&lt;/code&gt; receives the implementations instead of constructing them. Adding another implementation does not require changing the loop, provided the contract still represents the real payment behavior.&lt;/p&gt;
&lt;p&gt;If the rulers desired to add more payment methods to the &lt;em&gt;PaymentGateway&lt;/em&gt;, they wouldn&amp;rsquo;t have to touch the old code base as new payment methods could implement the &lt;strong&gt;IPaymentMethod&lt;/strong&gt; interface. And that&amp;rsquo;s how the land remained safe from bugs and well-maintained despite the addition of new features.&lt;/p&gt;
&lt;p&gt;In production code, payment methods normally represent alternatives rather than charging every method in an array. Treat this abbreviated example as an illustration of extension points, not a complete payment workflow.&lt;/p&gt;</description></item><item><title>Synology Script - How to Efficiently Move Items from Subfolders to Parent Folder</title><link>https://www.yopa.page/blog/2023-03-23-to-move-all-items-from-subfolders-to-its-top-level-folder-in-synology.html</link><pubDate>Thu, 23 Mar 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-03-23-to-move-all-items-from-subfolders-to-its-top-level-folder-in-synology.html</guid><description>
&lt;p&gt;Flattening a directory tree looks simple until two subfolders contain a file with the same name. A script also needs to handle spaces, newlines, partial failures, and files that are already at the destination level.&lt;/p&gt;
&lt;p&gt;Before moving anything, take or verify a recoverable backup. Synology documents Hyper Backup and Snapshot Replication as recovery options; RAID alone is not a backup. Run the script first against a temporary directory with representative filenames.&lt;/p&gt;
&lt;h2 id="preview-the-move"&gt;
&lt;a href="#preview-the-move" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Preview the move
&lt;/h2&gt;
&lt;p&gt;This Bash script targets only files below the first directory level. It prints the proposed source and destination and stops when a destination name already exists.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#!/bin/bash
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nb"&gt;set&lt;/span&gt; -euo pipefail
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;rootdir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;/volume1/target&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;find &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$rootdir&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; -mindepth &lt;span class="m"&gt;2&lt;/span&gt; -type f -print0 &lt;span class="p"&gt;|&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nv"&gt;IFS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;read&lt;/span&gt; -r -d &lt;span class="s1"&gt;&amp;#39;&amp;#39;&lt;/span&gt; source&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nv"&gt;destination&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$rootdir&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;$(&lt;/span&gt;basename &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$source&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="k"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; -e &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$destination&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;collision: %q -&amp;gt; %q\n&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$source&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$destination&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &amp;gt;&lt;span class="p"&gt;&amp;amp;&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;would move: %q -&amp;gt; %q\n&amp;#39;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$source&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$destination&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Review every collision instead of silently overwriting it. Decide whether duplicate names should be renamed, deduplicated, or left in place.&lt;/p&gt;
&lt;h2 id="perform-the-move"&gt;
&lt;a href="#perform-the-move" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Perform the move
&lt;/h2&gt;
&lt;p&gt;After verifying the backup and preview, replace the final &lt;code&gt;printf 'would move...'&lt;/code&gt; line with:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;mv -- &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$source&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="nv"&gt;$destination&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Synology DSM versions can ship different command implementations. Confirm that &lt;code&gt;mv --&lt;/code&gt; works on the NAS; if it does not, use &lt;code&gt;mv &amp;quot;$source&amp;quot; &amp;quot;$destination&amp;quot;&lt;/code&gt; and reject filenames beginning with &lt;code&gt;-&lt;/code&gt; before running the script. Run it from an administrator-controlled shell, not from an untrusted downloaded script.&lt;/p&gt;
&lt;p&gt;This moves files, not directories. Empty directories remain so they can be inspected before removal. Do not add automatic directory deletion until the moved files, permissions, applications, and backups have been verified.&lt;/p&gt;
&lt;p&gt;Official recovery references: &lt;a href="https://kb.synology.com/en-global/DSM/help/DSM/Tutorial/backup_backup"&gt;Back up your Synology NAS&lt;/a&gt; and &lt;a href="https://kb.synology.com/en-global/DSM/help/HyperBackup/data_backup?version=7"&gt;Hyper Backup data backup&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Understanding and Applying the Liskov Substitution Principle in Object-Oriented Programming</title><link>https://www.yopa.page/blog/2023-03-22-liskov-substitution-principle.html</link><pubDate>Wed, 22 Mar 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-03-22-liskov-substitution-principle.html</guid><description>
&lt;p&gt;The &lt;strong&gt;Liskov Substitution Principle&lt;/strong&gt; (LSP) is about behavioral compatibility. Code written for a base type should continue to work when it receives any valid subtype. Sharing a method name is not enough: the subtype must preserve the base type&amp;rsquo;s promises, including valid inputs, results, side effects, and invariants.&lt;/p&gt;
&lt;h2 id="let-say"&gt;
&lt;a href="#let-say" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Let say&amp;hellip;
&lt;/h2&gt;
&lt;p&gt;If &lt;code&gt;B&lt;/code&gt; is a subtype of &lt;code&gt;A&lt;/code&gt;, clients that depend on &lt;code&gt;A&lt;/code&gt; should not need type checks or special recovery logic when given &lt;code&gt;B&lt;/code&gt;. A subtype must not require stricter preconditions or provide weaker postconditions than its base contract.&lt;/p&gt;
&lt;h2 id="example"&gt;
&lt;a href="#example" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Example
&lt;/h2&gt;
&lt;p&gt;Now, let&amp;rsquo;s see an interesting example of LSP, shall we? Imagine you have a &lt;strong&gt;&lt;em&gt;Bird&lt;/em&gt;&lt;/strong&gt; base class and two subclasses: &lt;strong&gt;Penguin&lt;/strong&gt; and &lt;strong&gt;Eagle&lt;/strong&gt;. The &lt;strong&gt;Penguin&lt;/strong&gt; class cannot fly, whereas the &lt;strong&gt;Eagle&lt;/strong&gt; class can. However, both classes can make sounds by overriding the &lt;code&gt;makeSound()&lt;/code&gt; method of the &lt;strong&gt;Bird&lt;/strong&gt; base class.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Bird&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;makeSound&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Chirp chirp&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Penguin&lt;/span&gt; &lt;span class="kr"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Bird&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;makeSound&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Honk honk&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;class&lt;/span&gt; &lt;span class="nx"&gt;Eagle&lt;/span&gt; &lt;span class="kr"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Bird&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;makeSound&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Screech&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;fly&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Soaring through the skies&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Now, imagine you have a function called &lt;code&gt;letTheBirdsSing(Bird: Bird)&lt;/code&gt;, which calls the makeSound() method on the &lt;strong&gt;Bird&lt;/strong&gt; object passed to it. According to LSP, you should be able to pass in either a &lt;strong&gt;Penguin&lt;/strong&gt; or an &lt;strong&gt;Eagle&lt;/strong&gt; object, and the function should work as expected.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;letTheBirdsSing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bird&lt;/span&gt;: &lt;span class="kt"&gt;Bird&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;bird&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;makeSound&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;h2 id="where-a-violation-actually-appears"&gt;
&lt;a href="#where-a-violation-actually-appears" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Where a violation actually appears
&lt;/h2&gt;
&lt;p&gt;Here&amp;rsquo;s where it gets interesting.
The &lt;code&gt;Bird&lt;/code&gt; type above promises only &lt;code&gt;makeSound&lt;/code&gt;, so both subtypes satisfy that contract. Adding a &lt;code&gt;fly&lt;/code&gt; requirement to every bird would create the modeling error. A client should depend on a narrower capability instead:&lt;/p&gt;
&lt;p&gt;What can be a solution to this problem?&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;FlyingBird&lt;/span&gt; &lt;span class="kr"&gt;extends&lt;/span&gt; &lt;span class="nx"&gt;Bird&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;fly&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;letTheBirdFly&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;bird&lt;/span&gt;: &lt;span class="kt"&gt;FlyingBird&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;bird&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fly&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;This design makes the required behavior explicit and avoids an &lt;code&gt;instanceof&lt;/code&gt; branch. Composition and small capability interfaces are often clearer than forcing every real-world category into one inheritance hierarchy.&lt;/p&gt;</description></item><item><title>Understanding the YAGNI Principle in Software Development - Less is More</title><link>https://www.yopa.page/blog/2023-03-21-yagni.html</link><pubDate>Tue, 21 Mar 2023 01:25:00 -0400</pubDate><author>Yoonsoo Park</author><guid>https://www.yopa.page/blog/2023-03-21-yagni.html</guid><description>
&lt;p&gt;&lt;strong&gt;YAGNI — You Aren&amp;rsquo;t Gonna Need It&lt;/strong&gt; asks a team to avoid speculative features and abstractions until a real requirement justifies their cost. It is a prioritization rule, not an excuse to ignore known security, reliability, or migration work.&lt;/p&gt;
&lt;h2 id="the-problem"&gt;
&lt;a href="#the-problem" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Problem
&lt;/h2&gt;
&lt;p&gt;Many developers are guilty of &lt;strong&gt;over-engineering&lt;/strong&gt;, trying to add all sorts of features that might not even be needed. As you start coding, you might think &amp;ldquo;Oh, I could add this cool feature&amp;rdquo; or &amp;ldquo;I should probably build in the option to do this.&amp;rdquo; Stop right there! Before you go down that rabbit hole, ask yourself if you really need it.&lt;/p&gt;
&lt;h2 id="the-solution-yagni"&gt;
&lt;a href="#the-solution-yagni" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
The Solution: YAGNI
&lt;/h2&gt;
&lt;p&gt;The YAGNI principle suggests that you only implement what you &lt;strong&gt;absolutely need at the moment&lt;/strong&gt;. Don&amp;rsquo;t waste your time and energy building something that might never be used. Save yourself from the headache of maintaining unnecessary code.&lt;/p&gt;
&lt;h2 id="case-study"&gt;
&lt;a href="#case-study" class="anchor"&gt;
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16"&gt;
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z"&gt;
&lt;/path&gt;
&lt;/svg&gt;
&lt;/a&gt;
Case Study
&lt;/h2&gt;
&lt;p&gt;Let&amp;rsquo;s say you&amp;rsquo;re creating a simple app for ordering food online. Your first instinct might be to build in functionality for adding multiple addresses, tips, and advanced filtering options. But hold on a second, that&amp;rsquo;s not the main purpose of the app!&lt;/p&gt;
&lt;p&gt;Instead, focus on &lt;strong&gt;the core functionality&lt;/strong&gt;- allowing users to place an order with delivery method and payment. Everything else can come later when you know it is necessary.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Remember, less is more. Keep your codebase clean, lean, and mean. You&amp;rsquo;ll be thankful for it later.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&lt;/span&gt;&lt;span class="lnt"&gt;13
&lt;/span&gt;&lt;span class="lnt"&gt;14
&lt;/span&gt;&lt;span class="lnt"&gt;15
&lt;/span&gt;&lt;span class="lnt"&gt;16
&lt;/span&gt;&lt;span class="lnt"&gt;17
&lt;/span&gt;&lt;span class="lnt"&gt;18
&lt;/span&gt;&lt;span class="lnt"&gt;19
&lt;/span&gt;&lt;span class="lnt"&gt;20
&lt;/span&gt;&lt;span class="lnt"&gt;21
&lt;/span&gt;&lt;span class="lnt"&gt;22
&lt;/span&gt;&lt;span class="lnt"&gt;23
&lt;/span&gt;&lt;span class="lnt"&gt;24
&lt;/span&gt;&lt;span class="lnt"&gt;25
&lt;/span&gt;&lt;span class="lnt"&gt;26
&lt;/span&gt;&lt;span class="lnt"&gt;27
&lt;/span&gt;&lt;span class="lnt"&gt;28
&lt;/span&gt;&lt;span class="lnt"&gt;29
&lt;/span&gt;&lt;span class="lnt"&gt;30
&lt;/span&gt;&lt;span class="lnt"&gt;31
&lt;/span&gt;&lt;span class="lnt"&gt;32
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-typescript" data-lang="typescript"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Bad example
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;calculateTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;: &lt;span class="kt"&gt;Order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// ...calculate subtotal
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TAX_RATE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TIP_PERCENTAGE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;tip&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;tipPercentage&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;TIP_PERCENTAGE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TAX&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;tip&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;TAX_RATE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;tip&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;TAX&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isPickup&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DELIVERY_FEE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;DELIVERY_FEE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;order&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;isVIP&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="nx"&gt;TIP_PERCENTAGE&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;// Good example
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;calculateTotal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;tip&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;taxRate&lt;/span&gt;: &lt;span class="kt"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;isPickup&lt;/span&gt;: &lt;span class="kt"&gt;boolean&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;tip&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;subtotal&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nx"&gt;taxRate&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&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;isPickup&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kr"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DELIVERY_FEE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;DELIVERY_FEE&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;total&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;So next time you hear a voice in your head telling you to add a bunch of features to your codebase, take a deep breath and remember- YAGNI!
Your future self will thank you (0f course, &lt;strong&gt;and me&lt;/strong&gt;).&lt;/p&gt;
&lt;p&gt;Use YAGNI alongside evidence and risk assessment. A small reversible feature can wait for demand; an irreversible data choice or known compliance requirement may need deliberate preparation before it becomes urgent.&lt;/p&gt;</description></item></channel></rss>