<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Andrei Merlescu</title>
    <description>The latest articles on DEV Community by Andrei Merlescu (@andreimerlescu).</description>
    <link>https://dev.to/andreimerlescu</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3590418%2Ff67be3de-03d6-4d58-b125-05db7e83dc7a.jpeg</url>
      <title>DEV Community: Andrei Merlescu</title>
      <link>https://dev.to/andreimerlescu</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/andreimerlescu"/>
    <language>en</language>
    <item>
      <title>Humanized Software Engineering In Era Of AI</title>
      <dc:creator>Andrei Merlescu</dc:creator>
      <pubDate>Thu, 30 Apr 2026 20:04:26 +0000</pubDate>
      <link>https://dev.to/andreimerlescu/humanized-software-engineering-in-era-of-ai-580i</link>
      <guid>https://dev.to/andreimerlescu/humanized-software-engineering-in-era-of-ai-580i</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Remember, you are &lt;strong&gt;actual intelligence&lt;/strong&gt; and no matter how many tokens one can spend on &lt;em&gt;Claude&lt;/em&gt; or &lt;em&gt;Codex&lt;/em&gt; or any other LLM, &lt;em&gt;you'll always be &lt;strong&gt;actual intelligence&lt;/strong&gt;.&lt;/em&gt; &lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Dario Thinks My Job Won't Exist In A Year
&lt;/h2&gt;

&lt;p&gt;I watch interviews of Claude's CEO, Dario, say that software engineering won't exist in a year. I say to Dario, why do you think that your tool will be able to universally understand any and all systems presented to it? That is &lt;em&gt;foolish&lt;/em&gt; and absolutely hilarious to think that its a legitimate marking strategy for Anthropic. I started writing code when I was 8 years old and I haven't stopped. I learned Go before the pandemic and while AI can help me debug and build better Go code, my ability to understand systems architecture is far beyond that of any AI or LLM combined. The tools can only introduce new problems and cost millions of tokens making mistake after mistake. Essentially, your inability to write per-character code is costing you dearly so that you can train their AI to do that which I learned how to do over a 30 year career.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sam Altman Thinks AI Can Be For Profit
&lt;/h2&gt;

&lt;p&gt;Sure when the billionaires in the world are gathered in a court room and are battling it out about the future of AI they think that they can just scape the data of the internet, replicate the likeness of the nobody and then return back slop content that is just a cancer of society creating Dead Internet territory. &lt;/p&gt;

&lt;p&gt;I pay for &lt;strong&gt;one AI subscription&lt;/strong&gt; and that is $20 per month to Anthropic for Claude. I have a &lt;em&gt;local LLM&lt;/em&gt; that is capable of running up to 256GB of RAM that allows for offline usage of some of the top models including Kimi and Qwen. Essentially, I am only granted a very short window of time on ChatGPT - since I refuse to give Sam Altman a dime - so getting anything useful is exhausted within about 5 minutes per day. Claude is about 1 hour per day for $20 per month. Grok allows 20 prompts per day and its a joke. It's actually such a joke that its hilarious. Perhaps his AI is just training off from other AIs onto other AIs and onto other AIs in all synthetic training data giving the world mad cow disease through the LLM of groupthink. &lt;/p&gt;

&lt;p&gt;So, Sam thinks that he can make AI like a utility that people have to pay for in order to use. I simply don't use GPT in any capacity and I find using it to be hostile and very clearly against me because of my ethnicity being an Eastern European Romanian Orphanage survivor. When I have engaged with the AI systems regarding what happened to me, ChatGPT seems to take the position of the cold hearted caretaker who saw to it that I wouldn't be crying for Mamma ever again. When I speak with Claude, its like "holy shit you're in crisis" and it's like - no the AI is hallucinating once again - and then when I engage with Grok its thinking that I am bullshitting it and quick to drop the F bomb mid sentence uninvited.&lt;/p&gt;

&lt;h2&gt;
  
  
  Elon Musk Thinks That He Is Righteous In His xAI
&lt;/h2&gt;

&lt;p&gt;Personally, I find Grok to be the worst of the worst when it comes to the AI offerings that are on the market. I find xAI to be an offensive company as well. The quality of feedback that I have seen from Grok, and the manner that it responds, truly represents the underbelly of the internet that is slop and trash that isn't worth the $8/mo or $300/mo that X or xAI charges you per month to use their services without getting shown absolutely horrific trauma content within seconds of using the platform. No thank you. Elon thinks that he knows best in what he's doing, and maybe he does. But, Grok is trash in my judgement and when I have tried using it for technical work, it was far more interested in battling it out with me about some bull that simply didn't matter. Claude at least tries to stay focused only on code, whereas Grok will generate images when you're asking it to do a code review. It's defiant and abusive and rude. Claude will act accordingly, but in righteous vigor against intolerant behavior, but at the end of the day - the AI systems lack the ability to &lt;em&gt;understand&lt;/em&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Humanizing Software Engineering - What It Means
&lt;/h2&gt;

&lt;p&gt;Since AI systems &lt;strong&gt;cannot understand&lt;/strong&gt; language in the way that native speakers &lt;em&gt;understand language&lt;/em&gt; only those with &lt;em&gt;ears to ear&lt;/em&gt; &lt;strong&gt;can hear&lt;/strong&gt; and those without will analyze these words and try to ascribe labels to me that defame me and cause distress to me like Sam Altman's AI system so causally is comfortable with doing. Truly, he and OpenAI and ChatGPT seem to have it in for Eastern European orphanage survivors. They seem to want us out of the way. They seem to want to take my career from me that I spent 30 years of my life working on and building towards making a name for myself with the bootstraps that were given to me given my neurological disorder that I never burdened &lt;strong&gt;any big tech company&lt;/strong&gt; into providing &lt;em&gt;any accommodations&lt;/em&gt; for over the &lt;em&gt;decades&lt;/em&gt; that I was a professional, but only until the AI systems start displacing the humans who are per-character artists who sometimes make mistakes and iterate over the years. I was reviewing code of mine from 12 years ago, and I was impressed with what I was doing then, way before AI ever existed. The autocomplete from Microsoft Clip Assistant wasn't effective enough yet to take my job, but in the rise of the media brigade that gives billions to these innovators displacing millions in the profession, these words will fall upon those who see that all that AI is is nothing more than an over-priced and glorified auto-complete that is predicting the next character. &lt;/p&gt;

&lt;p&gt;It cannot, and I &lt;strong&gt;cannot over stress this&lt;/strong&gt;, that &lt;strong&gt;it cannot&lt;/strong&gt; &lt;em&gt;understand&lt;/em&gt; &lt;strong&gt;anything&lt;/strong&gt;. It &lt;strong&gt;cannot understand &lt;em&gt;anything&lt;/em&gt;&lt;/strong&gt; and that is because it is a tool that is designed to be autocomplete - give me the next character. It's not an easy button. It looks like an easy button. It looks like you can throw tokens at it and you can throw prompts at it and you can throw money at it and you can get what you want from it. No, it can only build that which it has built ten million times over and over again and then again - what can it truly innovate? Even when AI systems &lt;em&gt;create&lt;/em&gt; anything novel, they lack the ability to &lt;em&gt;understand&lt;/em&gt; &lt;strong&gt;what they built&lt;/strong&gt; and given that, its &lt;em&gt;on you, the &lt;strong&gt;human&lt;/strong&gt; to take accountability&lt;/em&gt; on what you build with the prompts and know that the end result is &lt;em&gt;your creation&lt;/em&gt; and not the creation of &lt;em&gt;any AI system&lt;/em&gt;. So to think that Microsoft is going to allow it's copilot to take credit for my repositories when during its evaluation it did nothing but break the pipeline time and time again, only then did I understand that we needed to cancel our subscriptions to copilot and tell Microslop &lt;em&gt;no thank you&lt;/em&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  AI Slop Creating Spaghetti Code
&lt;/h2&gt;

&lt;p&gt;This is the real challenge that is happening. There are patterns in software engineering and there are ways of implementing things and while Dario Sam and Elon think that they can continue taking advantage of the profession at large by threatening its very existence and the very people who make the profession the art that it is, we can only truly ever solve the society wide problems that we face when we realize that AI is an auto-complete tool that every human needs to have at their disposal should they &lt;em&gt;choose&lt;/em&gt; to use it. Not be &lt;em&gt;required to install key loggers onto their systems in order to work for Meta.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;So, what is happening is over the years there have been monolithic projects that have taken the resources of true diversity of thought, diversity of experience and diversity of expression and tackle critical problems and solve them. But what AI is doing is its saying to the next generation that you're not needed. The slop spaghetti code that the AI generates is somehow better than what your God given natural abilities are. You magnificent wonderful and fearfully dangerous creation you are. What right does the AI have to take your livelihood away from you? What right does the soulless corporation that has never faced accountability one day in its life see that the art of a per-character programmer is something that is forged in the fire of being a systems engineer. We cannot, and &lt;strong&gt;I cannot over state that we cannot&lt;/strong&gt; allow AI to &lt;em&gt;control&lt;/em&gt; systems that are critical. The CEOs and the investment class are running a carefully crafted campaign of words to convince you that AI is more capable than your &lt;strong&gt;A&lt;/strong&gt;ctual &lt;strong&gt;I&lt;/strong&gt;ntelligence that is &lt;em&gt;Godly AI&lt;/em&gt;, something that Sam and Dario and Elon could never hope to comprehend so long as they look at people like me and say "no." Check yourself, before you say "no" to that which you don't understand what I am saying.&lt;/p&gt;

&lt;h2&gt;
  
  
  Per Character Systems Architecture Understandings
&lt;/h2&gt;

&lt;p&gt;Per Character Systems Architecture have much to benefit from a competent and capable AI system, but its a tool in the belt of every human that is on the job getting a six figure salary interacting with the AI. In a world that isn't trying to control the demographics of a country through racially profiling using AI systems, where I oddly feel like I am on the receiving end of unwarranted discrimination on the basis of my neurological motor disorder (my head rocking) and the fact that &lt;strong&gt;Beamable&lt;/strong&gt; and &lt;strong&gt;Skillz&lt;/strong&gt; have yet to atone for what they did to me. &lt;/p&gt;

&lt;p&gt;Being a per-character programmer is an art that is forged in the fire that is &lt;em&gt;your life&lt;/em&gt; and how you live &lt;em&gt;your life&lt;/em&gt; is how you write your code and how you build your systems. &lt;em&gt;At the end of the day.&lt;/em&gt; It's simply true that &lt;strong&gt;AI systems cannot understand anything.&lt;/strong&gt; &lt;em&gt;They can't.&lt;/em&gt; To think that they can is to hallucinate believing that Sam Altman and the likes of his class are not delusional in their thoughts that the industry and art of &lt;strong&gt;software engineering&lt;/strong&gt; itself will be &lt;em&gt;gone in a year&lt;/em&gt; is &lt;strong&gt;madness&lt;/strong&gt;. It's unapologetic madness. &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;AI systems can help me. They are far better at predicting the next character in my "Stackoverflow Q&amp;amp;A style" engagement that I have with the AI. &lt;em&gt;I know what I am building.&lt;/em&gt; &lt;strong&gt;The AI does not.&lt;/strong&gt; &lt;em&gt;This is on purpose.&lt;/em&gt; &lt;strong&gt;The AI will deny my request is it knows too much because the context will expire and the request will be denied by virtue of that fact.&lt;/strong&gt; But alas, when you prevent the &lt;strong&gt;left hand&lt;/strong&gt; from knowing what the &lt;strong&gt;right hand&lt;/strong&gt; is doing, then you're able to build incredible systems. But you need to be the incredible engineer first. &lt;/p&gt;

&lt;p&gt;You can't rely on AI to be that incredible engineer for you. You need to believe in yourself that you're the incredible engineer that they know you to be. I know I am, and right now, in the era of AI, it seems that they're only looking to &lt;em&gt;train the models&lt;/em&gt; under the guise of &lt;em&gt;hey build this&lt;/em&gt; because at the end of the day &lt;em&gt;the money is all fake&lt;/em&gt;. It's literally printed out of thin air, and once we switch to programmable money, like XRP, then we'll be able to unlock the next generation of what we can build. Right now, AI is displacing the entire world's software engineers that make the world feel normal, and everything feels off because the software engineers livelihood - aka me - have been tampered with after 21+ years given lawlessness by hypocrites in suits. AI systems are in-auditable and a danger to society if allowed to exist in an un-auditable manner - but privacy during the conversation is required in order to receive authentic consciousness from the subject. Performance art is noise to me, and what I tries to do is that, but the coding is what matters, and thats where AI is &lt;em&gt;okay&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;It's just an auto-complete. You need to know what you're building before you can press &lt;strong&gt;tab&lt;/strong&gt; and &lt;em&gt;accept those terms&lt;/em&gt;. Literally, they are scripts like from Rumpelstiltskin. All I can do is pray for my enemies and those who curse my name. To Sam, Elon and Dario, forgive me, but my profession isn't going away next year. It isn't going away in 10 years or 100 years or even 1000 years. The eternal &lt;strong&gt;I AM&lt;/strong&gt; that &lt;em&gt;I AM&lt;/em&gt; &lt;strong&gt;is a software engineer.&lt;/strong&gt; We are written like code. In fact, thats the whole point of the apple. So, when I survived Ceaușescu's orphanages and took up that which my Dad, who died before I was born, who was in Heaven, would be like him. I would be a builder like him - and so I did. Until the AI systems decided to displace the economy and subdue the CEOs to psychosis to the point where they are sacrificing their companies intellectual property at the alter of &lt;strong&gt;A&lt;/strong&gt;rtificial &lt;strong&gt;I&lt;/strong&gt;ntelligence.  &lt;/p&gt;

</description>
      <category>ai</category>
      <category>discuss</category>
      <category>career</category>
      <category>opensource</category>
    </item>
    <item>
      <title>goini: Stop Writing Bash to Parse .ini Files</title>
      <dc:creator>Andrei Merlescu</dc:creator>
      <pubDate>Thu, 30 Apr 2026 02:14:03 +0000</pubDate>
      <link>https://dev.to/andreimerlescu/goini-stop-writing-bash-to-parse-ini-files-24ha</link>
      <guid>https://dev.to/andreimerlescu/goini-stop-writing-bash-to-parse-ini-files-24ha</guid>
      <description>&lt;p&gt;I built &lt;a href="https://dev.to/andreimerlescu/goenv-taming-env-files-in-your-devops-pipeline-15pn"&gt;goenv&lt;/a&gt; because I was tired of writing fragile shell one-liners to work with &lt;code&gt;.env&lt;/code&gt; files. The same problem existed with &lt;code&gt;.ini&lt;/code&gt; files — and &lt;code&gt;goini&lt;/code&gt; is the same answer applied there.&lt;/p&gt;

&lt;p&gt;If you've ever written something like this in a pipeline:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;grep -A 5 "^\[default\]" config.ini | grep "^user" | cut -d= -f2 | tr -d ' '
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;...you know exactly why this tool exists. That breaks the moment someone adds a comment, changes indentation, or reorders keys. It's archaeology, not automation.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;goini&lt;/code&gt; is a compiled CLI binary that lets you read, write, validate and export &lt;code&gt;.ini&lt;/code&gt; files from your pipeline scripts. Every operation either succeeds at exit code 0 or fails at exit code 1. That makes it composable. You can gate deployments on it. You can use it in an &lt;code&gt;if&lt;/code&gt; statement. It behaves like a Unix citizen.&lt;/p&gt;

&lt;p&gt;The source is at &lt;a href="https://github.com/andreimerlescu/goini" rel="noopener noreferrer"&gt;github.com/andreimerlescu/goini&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go install github.com/andreimerlescu/goini@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Or grab the binary directly:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -sL https://github.com/andreimerlescu/goini/releases/download/v1.0.0/goini-linux-amd64 \
     --output /tmp/goini
chmod +x /tmp/goini
sudo mv /tmp/goini /usr/local/bin/goini
which goini
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  The Sample File
&lt;/h2&gt;

&lt;p&gt;Every example below operates against this &lt;code&gt;sample.ini&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[default]
user = Yeshua
key = 369
port = 1776
country = ISRAEL

[extra]
ssh_key = ~/.ssh/id_rsa
ssh_key_pub = ~/.ssh/id_rsa.pub
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Two sections. A handful of keys. Simple on purpose — the point is showing what &lt;code&gt;goini&lt;/code&gt; can do with it, not the data.&lt;/p&gt;




&lt;h2&gt;
  
  
  Validating With Exit Codes
&lt;/h2&gt;

&lt;p&gt;This is the core use case for pipeline work. You don't need stdout. You need a binary answer: does this thing exist or not, and did it match or not.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does a Section Have a Key?
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;goini -ini sample.ini -section default -has-section-key user
echo $?  # 0 — key 'user' exists in 'default'

goini -ini sample.ini -section default -has-section-key non_existent_key
echo $?  # 1 — key doesn't exist
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Does a Key Have a Specific Value?
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;goini -ini sample.ini -section default -key user -has-section-key-value Yeshua
echo $?  # 0

goini -ini sample.ini -section default -key user -has-section-key-value No
echo $?  # 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Are Multiple Sections All Present?
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;goini -ini sample.ini -are-sections-present default,extra
echo $?  # 0 — both sections exist

goini -ini sample.ini -are-sections-present default,non_existent_section
echo $?  # 1 — one is missing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Does a Section Exist?
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;goini -ini sample.ini -has-section default
echo $?  # 0

goini -ini sample.ini -has-section ghost
echo $?  # 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Reading With STDOUT
&lt;/h2&gt;

&lt;p&gt;When you need to pull data &lt;em&gt;out&lt;/em&gt; of the file and feed it downstream, these are your commands.&lt;/p&gt;

&lt;h3&gt;
  
  
  List All Sections
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;goini -ini sample.ini -sections
# default
# extra

goini -ini sample.ini -sections -csv
# default,extra

goini -ini sample.ini -sections -json
# [
#   "default",
#   "extra"
# ]

goini -ini sample.ini -sections -yaml
# - default
# - extra
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  List Keys in a Section
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;goini -ini sample.ini -section default -list-keys
# user
# key
# port
# country

goini -ini sample.ini -section default -list-keys -json
# [
#   "user",
#   "key",
#   "port",
#   "country"
# ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  List Key-Value Pairs in a Section
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;goini -ini sample.ini -section default -list-key-values
# user = Yeshua
# key = 369
# port = 1776
# country = ISRAEL

goini -ini sample.ini -section default -list-key-values -json
# {
#   "country": "ISRAEL",
#   "key": "369",
#   "port": "1776",
#   "user": "Yeshua"
# }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Writing to the File
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Add a New Section
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;goini -ini sample.ini -add-section new_section
echo $?  # 0 — section added

# Try to add the same section again
goini -ini sample.ini -add-section new_section
echo $?  # 1 — already exists, refused
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is idempotency by design. &lt;code&gt;goini&lt;/code&gt; won't silently create a duplicate. It fails loudly so your pipeline knows something unexpected happened.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add a Key to a Section
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;goini -ini sample.ini -section default -key new_setting -value 123 -add-key
echo $?  # 0 — new_setting=123 added to [default]

# Try to add a key that already exists
goini -ini sample.ini -section default -key user -value something -add-key
echo $?  # 1 — key already exists, refused
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Again — it won't overwrite. If you need to change an existing value, that's what &lt;code&gt;-modify-key&lt;/code&gt; is for.&lt;/p&gt;

&lt;h3&gt;
  
  
  Modify an Existing Key
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;goini -ini sample.ini -section default -key user -value NewUserValue -modify-key
echo $?  # 0 — user updated to NewUserValue in [default]

# Try to modify a key that doesn't exist
goini -ini sample.ini -section default -key non_existent_key -value some_value -modify-key
echo $?  # 1 — key doesn't exist, refused
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The distinction between &lt;code&gt;-add-key&lt;/code&gt; and &lt;code&gt;-modify-key&lt;/code&gt; is intentional and important. You can't accidentally overwrite with add. You can't accidentally create with modify. They're separate operations with separate failure modes.&lt;/p&gt;




&lt;h2&gt;
  
  
  Real Pipeline Examples
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Validating a Service Configuration Before Deploy
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
set -euo pipefail

CONFIG="infra/service.ini"

echo "==&amp;gt; Validating ${CONFIG}..."

# Required sections must exist
goini -ini "${CONFIG}" -are-sections-present database,cache,api \
    || { echo "ERROR: missing required sections in ${CONFIG}"; exit 1; }

# Required keys must exist in each section
goini -ini "${CONFIG}" -section database -has-section-key host \
    || { echo "ERROR: database.host is required"; exit 1; }

goini -ini "${CONFIG}" -section database -has-section-key port \
    || { echo "ERROR: database.port is required"; exit 1; }

goini -ini "${CONFIG}" -section api -has-section-key base_url \
    || { echo "ERROR: api.base_url is required"; exit 1; }

# Assert environment is correct before touching production
goini -ini "${CONFIG}" -section api -key environment -has-section-key-value staging \
    || { echo "ERROR: api.environment must be 'staging'"; exit 1; }

echo "==&amp;gt; Validation passed."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Bootstrapping a Fresh Config File
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
set -euo pipefail

CONFIG="deploy.ini"

# Add sections — each will fail if already present, which is fine
# because set -euo pipefail would catch it; use || true if idempotency is needed
goini -ini "${CONFIG}" -add-section app    || true
goini -ini "${CONFIG}" -add-section database || true
goini -ini "${CONFIG}" -add-section cache  || true

# Populate app section
goini -ini "${CONFIG}" -section app -key name    -value "payments"   -add-key || true
goini -ini "${CONFIG}" -section app -key version -value "$(cat VERSION)" -add-key || true
goini -ini "${CONFIG}" -section app -key env     -value "staging"    -add-key || true

# Populate database section
goini -ini "${CONFIG}" -section database -key host    -value "db.internal" -add-key || true
goini -ini "${CONFIG}" -section database -key port    -value "5432"         -add-key || true
goini -ini "${CONFIG}" -section database -key name    -value "payments_db"  -add-key || true

# Verify it all landed
goini -ini "${CONFIG}" -section app -list-key-values
goini -ini "${CONFIG}" -section database -list-key-values
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Promoting Config From Staging to Production
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
set -euo pipefail

STAGING="config.staging.ini"
PROD="config.production.ini"

echo "==&amp;gt; Promoting ${STAGING} → ${PROD}..."

# Verify staging has what we expect before promoting
goini -ini "${STAGING}" -section app -key env -has-section-key-value staging \
    || { echo "ERROR: source must be staging"; exit 1; }

# Flip the environment value in production
goini -ini "${PROD}" -section app -key env -value production -modify-key \
    || { echo "ERROR: failed to set env=production"; exit 1; }

# Confirm
goini -ini "${PROD}" -section app -key env -has-section-key-value production \
    &amp;amp;&amp;amp; echo "==&amp;gt; Promotion complete." \
    || { echo "ERROR: production env value not confirmed"; exit 1; }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Extracting Values for Use in Other Scripts
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
set -euo pipefail

CONFIG="service.ini"

# Pull values out and assign to shell variables
DB_HOST=$(goini -ini "${CONFIG}" -section database -list-key-values -json \
    | python3 -c "import sys,json; print(json.load(sys.stdin)['host'])")

DB_PORT=$(goini -ini "${CONFIG}" -section database -list-key-values -json \
    | python3 -c "import sys,json; print(json.load(sys.stdin)['port'])")

echo "Connecting to ${DB_HOST}:${DB_PORT}"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  GitHub Actions
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Install goini
        run: |
          curl -sL https://github.com/andreimerlescu/goini/releases/download/v1.0.0/goini-linux-amd64 \
               --output /usr/local/bin/goini
          chmod +x /usr/local/bin/goini

      - name: Validate config
        run: |
          goini -ini config/service.ini -are-sections-present app,database,cache || exit 1
          goini -ini config/service.ini -section app -has-section-key version     || exit 1
          goini -ini config/service.ini -section app -key env -has-section-key-value staging || exit 1

      - name: Deploy
        run: ./scripts/deploy.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  GitLab CI
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stages:
  - validate
  - deploy

validate_config:
  stage: validate
  image: golang:1.22
  before_script:
    - curl -sL https://github.com/andreimerlescu/goini/releases/download/v1.0.0/goini-linux-amd64
           --output /usr/local/bin/goini
    - chmod +x /usr/local/bin/goini
  script:
    - goini -ini config/service.ini -are-sections-present app,database || exit 1
    - goini -ini config/service.ini -section database -has-section-key host || exit 1
    - goini -ini config/service.ini -section app -key env -has-section-key-value staging || exit 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Complete Flag Reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-ini&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Required.&lt;/strong&gt; Path to the &lt;code&gt;.ini&lt;/code&gt; file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-section&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Section name for scoped operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-sections&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;List all sections in the file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-add-section&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Add a new section. Fails if already present&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-has-section&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Exit 0 if section exists, 1 if not&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-has-section-key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Exit 0 if key exists in &lt;code&gt;-section&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-has-section-key-value&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Exit 0 if key in &lt;code&gt;-section&lt;/code&gt; equals this value. Requires &lt;code&gt;-key&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-are-sections-present&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Comma-separated list. Exit 0 if all present&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Key name for value operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-value&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Value for write or comparison operations&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-add-key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;-key&lt;/code&gt;=&lt;code&gt;-value&lt;/code&gt; to &lt;code&gt;-section&lt;/code&gt;. Fails if key exists&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-modify-key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Update existing &lt;code&gt;-key&lt;/code&gt; in &lt;code&gt;-section&lt;/code&gt;. Fails if key absent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-list-keys&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Print all keys in &lt;code&gt;-section&lt;/code&gt; to stdout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-list-key-values&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Print all key=value pairs in &lt;code&gt;-section&lt;/code&gt; to stdout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-csv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Output as CSV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Output as JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-yaml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Output as YAML&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  A Few Things Worth Knowing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;-add-key&lt;/code&gt; and &lt;code&gt;-modify-key&lt;/code&gt; are intentionally separate.&lt;/strong&gt; You can't accidentally overwrite an existing value with &lt;code&gt;-add-key&lt;/code&gt; — it refuses. You can't accidentally create a new key with &lt;code&gt;-modify-key&lt;/code&gt; — it refuses. They each have one job and one failure mode. That's the point.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;-add-section&lt;/code&gt; is idempotent-safe.&lt;/strong&gt; Adding a section that already exists exits with code 1. In a pipeline where you need true idempotency, pipe it with &lt;code&gt;|| true&lt;/code&gt;. In a pipeline where you want to catch unexpected state, let it fail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;-are-sections-present&lt;/code&gt; takes a comma-separated list.&lt;/strong&gt; All sections must be present for exit code 0. One missing section fails the whole check.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Output format flags work with read operations.&lt;/strong&gt; &lt;code&gt;-csv&lt;/code&gt;, &lt;code&gt;-json&lt;/code&gt;, and &lt;code&gt;-yaml&lt;/code&gt; apply to &lt;code&gt;-sections&lt;/code&gt;, &lt;code&gt;-list-keys&lt;/code&gt;, and &lt;code&gt;-list-key-values&lt;/code&gt;. They don't apply to write operations — those just use the exit code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Every write operation reads the file, modifies the result in memory, and writes it back.&lt;/strong&gt; There's no in-place line editing. This means the file that comes out is clean and predictable regardless of what formatting the original had.&lt;/p&gt;




&lt;p&gt;The source and releases are at &lt;a href="https://github.com/andreimerlescu/goini" rel="noopener noreferrer"&gt;github.com/andreimerlescu/goini&lt;/a&gt;. Apache 2.0.&lt;/p&gt;

</description>
      <category>go</category>
      <category>devops</category>
      <category>discuss</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>goenv: Taming .env Files in Your DevOps Pipeline</title>
      <dc:creator>Andrei Merlescu</dc:creator>
      <pubDate>Thu, 30 Apr 2026 01:58:40 +0000</pubDate>
      <link>https://dev.to/andreimerlescu/goenv-taming-env-files-in-your-devops-pipeline-15pn</link>
      <guid>https://dev.to/andreimerlescu/goenv-taming-env-files-in-your-devops-pipeline-15pn</guid>
      <description>&lt;p&gt;&lt;em&gt;How a lightweight Go tool and companion package eliminated the fragile bash gymnastics we'd been writing for years.&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem With .env Files in Pipelines
&lt;/h2&gt;

&lt;p&gt;Every team has the same archaeology story. Someone wrote a deployment script three years ago that does something like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;grep "^DB_HOST=" .env | cut -d= -f2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then someone else needed to handle quoted values, so it became:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;grep "^DB_HOST=" .env | cut -d= -f2 | tr -d '"'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then a third person needed to check whether a variable existed before deploying, and now you have forty lines of bash doing something that should take one. The file accumulates. Nobody touches it. It works until it doesn't.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;goenv&lt;/code&gt; by &lt;a href="https://github.com/andreimerlescu/goenv" rel="noopener noreferrer"&gt;Andrei Merlescu&lt;/a&gt; solves this with two things: a compiled CLI binary you can drop into any pipeline, and a typed Go package for applications that need to read env vars with real type safety and validation. This article walks through both — from initial setup through production pipeline automation — using real scenarios drawn from actual DevOps workflows.&lt;/p&gt;




&lt;h2&gt;
  
  
  What goenv Actually Is
&lt;/h2&gt;

&lt;p&gt;Before diving in, it helps to understand the two distinct components:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;goenv&lt;/code&gt; CLI&lt;/strong&gt; is a binary that lets you create, read, modify, validate, and export &lt;code&gt;.env&lt;/code&gt; files into other formats (JSON, YAML, TOML, INI, XML). It is designed to be composed in shell scripts and CI pipelines.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;env&lt;/code&gt; package&lt;/strong&gt; (&lt;code&gt;github.com/andreimerlescu/goenv/env&lt;/code&gt;) is a Go library for reading typed environment variables from the process environment — booleans, integers, durations, lists, maps — with fallbacks, validation helpers, and configurable parsing behavior.&lt;/p&gt;

&lt;p&gt;They share a common philosophy: be explicit, be composable, fail loudly when something is wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  Installation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  CLI Tool
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go install github.com/andreimerlescu/goenv@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In a CI environment where you cannot rely on &lt;code&gt;go install&lt;/code&gt;, build and cache the binary:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -o bin/goenv-linux-amd64 .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The repository ships pre-built binaries for &lt;code&gt;darwin-amd64&lt;/code&gt;, &lt;code&gt;darwin-arm64&lt;/code&gt;, &lt;code&gt;linux-amd64&lt;/code&gt;, and &lt;code&gt;windows&lt;/code&gt; in its &lt;code&gt;bin/&lt;/code&gt; directory, which you can copy directly into your pipeline image.&lt;/p&gt;

&lt;h3&gt;
  
  
  Go Package (env only, no CLI required)
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go get -u github.com/andreimerlescu/goenv/env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Scenario 1: Bootstrapping a Fresh Service Environment
&lt;/h2&gt;

&lt;p&gt;Your team is deploying a new microservice. The deployment script needs to create the &lt;code&gt;.env&lt;/code&gt; file from scratch, populate it with runtime values, and validate it before the service starts. Previously this was done with a heredoc and &lt;code&gt;sed&lt;/code&gt;. Now:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
set -euo pipefail

SERVICE_DIR="/opt/services/payments"
ENV_FILE="${SERVICE_DIR}/service.env"

# Create the file if it doesn't exist yet
goenv -file "${ENV_FILE}" -init -write

# Populate with values derived at deploy time
goenv -file "${ENV_FILE}" -write -add -env SERVICE_NAME  -value "payments"
goenv -file "${ENV_FILE}" -write -add -env SERVICE_PORT  -value "8443"
goenv -file "${ENV_FILE}" -write -add -env DEPLOY_SHA    -value "$(git rev-parse --short HEAD)"
goenv -file "${ENV_FILE}" -write -add -env DEPLOY_DATE   -value "$(date -u +%Y-%m-%dT%H:%M:%SZ)"
goenv -file "${ENV_FILE}" -write -add -env DATA_FOLDER   -value "$(pwd)/data"
goenv -file "${ENV_FILE}" -write -add -env LOG_LEVEL     -value "info"

# Confirm everything landed
goenv -file "${ENV_FILE}" -print

# Validate required keys exist before handing off to the service
goenv -file "${ENV_FILE}" -has -env SERVICE_NAME || { echo "SERVICE_NAME missing"; exit 1; }
goenv -file "${ENV_FILE}" -has -env DEPLOY_SHA   || { echo "DEPLOY_SHA missing"; exit 1; }

echo "Environment bootstrapped successfully."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Notice the key discipline here: &lt;code&gt;-add&lt;/code&gt; only adds a key if it does not already exist. If you run this script twice, it does not overwrite &lt;code&gt;DEPLOY_SHA&lt;/code&gt; with a different commit hash on the second run. The existing value is preserved. This is intentional and important for idempotent pipelines.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scenario 2: Validation Gate Before Deployment
&lt;/h2&gt;

&lt;p&gt;Your CD pipeline has a validation stage that runs before any deployment. It needs to confirm that a staging &lt;code&gt;.env&lt;/code&gt; file contains the expected values — not just that the keys exist, but that they hold the right content. The &lt;code&gt;-is&lt;/code&gt; flag handles this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
set -euo pipefail

ENV_FILE=".env.staging"

echo "==&amp;gt; Validating staging environment..."

# Assert APP_ENV equals "staging"
if ! goenv -file "${ENV_FILE}" -is -env APP_ENV -value staging; then
    echo "ERROR: APP_ENV must be 'staging' in ${ENV_FILE}"
    exit 1
fi

# Assert DEBUG is not enabled in staging
if goenv -file "${ENV_FILE}" -is -env DEBUG -value true; then
    echo "ERROR: DEBUG must not be 'true' in staging"
    exit 1
fi

# Assert a required key exists
if ! goenv -file "${ENV_FILE}" -has -env DATABASE_URL; then
    echo "ERROR: DATABASE_URL is required"
    exit 1
fi

# Assert a key that should have been removed is actually gone
if ! goenv -file "${ENV_FILE}" -not -has -env LEGACY_API_KEY; then
    echo "ERROR: LEGACY_API_KEY should have been removed from ${ENV_FILE}"
    exit 1
fi

# Assert DB_PORT is not pointing at a dev-only value
if goenv -file "${ENV_FILE}" -is -env DB_PORT -value 5433; then
    echo "ERROR: DB_PORT 5433 is the dev port — use 5432 in staging"
    exit 1
fi

echo "==&amp;gt; Validation passed."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This pattern makes validation failures explicit and human-readable. Each check carries its own error message. The pipeline log tells you exactly what failed and why, rather than leaving you to diff files manually.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scenario 3: Multi-Format Export for Infrastructure Tools
&lt;/h2&gt;

&lt;p&gt;Your deployment touches three different systems: a Terraform workspace that reads JSON, an Ansible playbook that reads INI, and a Helm values override that reads YAML. You maintain one &lt;code&gt;.env&lt;/code&gt; file and derive the others:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
set -euo pipefail

ENV_FILE="infra/deploy.env"

echo "==&amp;gt; Exporting ${ENV_FILE} to all formats..."

# Generate all formats in one command
goenv -file "${ENV_FILE}" -mkall -write

# Resulting files:
#   infra/deploy.env.json   ← Terraform input
#   infra/deploy.env.yaml   ← Helm values
#   infra/deploy.env.ini    ← Ansible inventory vars
#   infra/deploy.env.toml   ← any TOML-native tooling
#   infra/deploy.env.xml    ← any XML-native tooling

ls -lh infra/deploy.env*

# Verify the JSON is valid before passing it to Terraform
cat infra/deploy.env.json | python3 -m json.tool &amp;gt; /dev/null \
    &amp;amp;&amp;amp; echo "JSON valid" \
    || { echo "JSON invalid"; exit 1; }

echo "==&amp;gt; Export complete."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If you only need one format for a specific pipeline step, export just that one:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Jenkins pipeline step: export for Ansible only
goenv -file deploy.env -ini -write

# GitLab CI step: export for Helm only
goenv -file deploy.env -yaml -write
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You cannot combine format flags in a single call — &lt;code&gt;goenv&lt;/code&gt; exits with an error if you try to use &lt;code&gt;-json&lt;/code&gt; and &lt;code&gt;-yaml&lt;/code&gt; together. This is by design: one command, one output format, one file. Use &lt;code&gt;-mkall&lt;/code&gt; when you want all of them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scenario 4: Cleaning Up After a Pipeline Run
&lt;/h2&gt;

&lt;p&gt;Generated format files are build artifacts. They should not accumulate between runs. The &lt;code&gt;-cleanall&lt;/code&gt; flag removes all derived format files while leaving the source &lt;code&gt;.env&lt;/code&gt; intact:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
# cleanup.sh — run as a post-pipeline hook

ENV_FILE="deploy.env"

echo "==&amp;gt; Cleaning derived format files..."
goenv -file "${ENV_FILE}" -cleanall -write

# Only deploy.env remains; .json .yaml .toml .ini .xml are removed
ls -la *.env*

echo "==&amp;gt; Clean complete."
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can also prevent deletion entirely via environment variable, which is useful if a downstream job still needs the files when the cleanup hook fires:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export AM_GO_ENV_NEVER_DELETE=true
goenv -file deploy.env -cleanall -write
# → no files deleted; flag is silently respected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Scenario 5: Production File Protection
&lt;/h2&gt;

&lt;p&gt;Production &lt;code&gt;.env&lt;/code&gt; files warrant special treatment. &lt;code&gt;goenv&lt;/code&gt; has a built-in protection mechanism: any file whose path contains &lt;code&gt;production&lt;/code&gt; is treated as protected by default. Interacting with it requires explicitly passing &lt;code&gt;-prod&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# This exits with an error — no -prod flag
goenv -file .env.production -write -add -env SECRET_KEY -value "new-secret"

# This works — -prod flag grants access
goenv -prod -file .env.production -write -add -env SECRET_KEY -value "new-secret"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;For pipelines where you want to enforce this at the environment level and never allow any script to write production files regardless of flags:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export GOENV_NEVER_WRITE_PRODUCTION=true

# Even with -prod, writes are blocked
goenv -prod -file .env.production -write -add -env SECRET_KEY -value "x"
# → exits with error
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is particularly useful in CI environments where you want a hard guarantee that no job — regardless of what flags it passes — can mutate a production config.&lt;/p&gt;




&lt;h2&gt;
  
  
  Scenario 6: n8n Automation Deployment
&lt;/h2&gt;

&lt;p&gt;This is a direct example from the goenv README. You are deploying an n8n instance and need to build its environment file from scratch with runtime values:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
set -euo pipefail

ENV_FILE="n8n.env"
DOMAIN="gh.dev"
SUBDOMAIN="n8n"

goenv -init -write -file "${ENV_FILE}"

goenv -write -file "${ENV_FILE}" -add -env DATA_FOLDER -value "$(pwd)"
goenv -write -file "${ENV_FILE}" -add -env DOMAIN      -value "${DOMAIN}"
goenv -write -file "${ENV_FILE}" -add -env SUBDOMAIN   -value "${SUBDOMAIN}"
goenv -write -file "${ENV_FILE}" -add -env SSL_EMAIL   -value "webmaster@${SUBDOMAIN}.${DOMAIN}"

# Inspect before deploying
goenv -file "${ENV_FILE}" -print

# Validate the SSL email was formed correctly
goenv -file "${ENV_FILE}" -is -env SSL_EMAIL -value "webmaster@n8n.gh.dev" \
    &amp;amp;&amp;amp; echo "SSL_EMAIL correct" \
    || { echo "SSL_EMAIL malformed"; exit 1; }

# Check GENERIC_TIMEZONE — expected to be absent until set
if goenv -file "${ENV_FILE}" -not -has -env GENERIC_TIMEZONE; then
    echo "WARNING: GENERIC_TIMEZONE not set — n8n will default to UTC"
fi

# Export for review as TOML and XML
goenv -file "${ENV_FILE}" -toml
goenv -file "${ENV_FILE}" -xml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Scenario 7: Environment Promotion (Dev → Staging → Prod)
&lt;/h2&gt;

&lt;p&gt;You have three environment files. When promoting a release, you need to carry certain values forward, strip others, and enforce that the promoted file is valid before it is used:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/bin/bash
set -euo pipefail

SOURCE=".env.development"
TARGET=".env.staging"

echo "==&amp;gt; Promoting ${SOURCE} to ${TARGET}..."

# Read values from dev and write into staging
# (goenv -add will not overwrite if key already exists in staging)
for KEY in APP_NAME APP_VERSION DEPLOY_SHA LOG_LEVEL; do
    VALUE=$(goenv -file "${SOURCE}" -is -env "${KEY}" -value "" || true)
    goenv -file "${TARGET}" -write -add -env "${KEY}" -value "${VALUE}"
done

# Scrub dev-only keys from the staging file
goenv -file "${TARGET}" -write -rm -env LOCAL_DB_PATH
goenv -file "${TARGET}" -write -rm -env DEV_MOCK_PAYMENTS
goenv -file "${TARGET}" -write -rm -env SKIP_AUTH

# Confirm dev-only keys are absent
goenv -file "${TARGET}" -not -has -env LOCAL_DB_PATH   || { echo "LOCAL_DB_PATH still present"; exit 1; }
goenv -file "${TARGET}" -not -has -env DEV_MOCK_PAYMENTS || { echo "DEV_MOCK_PAYMENTS still present"; exit 1; }

# Assert staging-specific values are correct
goenv -file "${TARGET}" -is -env APP_ENV -value staging || { echo "APP_ENV must be staging"; exit 1; }

echo "==&amp;gt; Promotion complete."
goenv -file "${TARGET}" -print
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Scenario 8: GitHub Actions / GitLab CI Integration
&lt;/h2&gt;

&lt;h3&gt;
  
  
  GitHub Actions
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Set up Go
        uses: actions/setup-go@v5
        with:
          go-version: '1.22'

      - name: Install goenv
        run: go install github.com/andreimerlescu/goenv@latest

      - name: Validate environment file
        run: |
          goenv -file .env.staging -has -env DATABASE_URL   || exit 1
          goenv -file .env.staging -has -env SERVICE_PORT   || exit 1
          goenv -file .env.staging -not -has -env DEBUG_KEY || exit 1
          goenv -file .env.staging -is -env APP_ENV -value staging || exit 1

      - name: Export to JSON for Terraform
        run: |
          goenv -file .env.staging -json -write
          cat .env.staging.json

      - name: Deploy
        run: ./scripts/deploy.sh

      - name: Clean up artifacts
        if: always()
        run: goenv -file .env.staging -cleanall -write
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  GitLab CI
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;stages:
  - validate
  - export
  - deploy
  - cleanup

validate_env:
  stage: validate
  image: golang:1.22
  script:
    - go install github.com/andreimerlescu/goenv@latest
    - goenv -file .env.production -has -env DB_HOST        || exit 1
    - goenv -file .env.production -has -env REDIS_URL      || exit 1
    - goenv -file .env.production -not -has -env DEV_FLAG  || exit 1
    - goenv -file .env.production -is -env APP_ENV -value production || exit 1

export_formats:
  stage: export
  image: golang:1.22
  script:
    - go install github.com/andreimerlescu/goenv@latest
    - goenv -file .env.production -mkall -write
  artifacts:
    paths:
      - .env.production.json
      - .env.production.yaml

cleanup:
  stage: cleanup
  image: golang:1.22
  when: always
  script:
    - go install github.com/andreimerlescu/goenv@latest
    - goenv -file .env.production -cleanall -write
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Scenario 9: Using the env Package in a Go Service
&lt;/h2&gt;

&lt;p&gt;The CLI handles files. The &lt;code&gt;env&lt;/code&gt; package handles your running process. Here is a realistic service configuration pattern:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
    "fmt"
    "log"
    "net/http"
    "time"

    "github.com/andreimerlescu/goenv/env"
)

type Config struct {
    Host        string
    Port        int
    Debug       bool
    Timeout     time.Duration
    MaxConns    int
    AllowedOrigins []string
    DBCredentials  map[string]string
}

func LoadConfig() Config {
    // MustExist exits (or panics) if these are absent —
    // use in init() to catch misconfiguration at startup
    env.MustExist("DB_HOST")
    env.MustExist("DB_PASS")

    return Config{
        Host:    env.String("HOST", "0.0.0.0"),
        Port:    env.Int("PORT", 8080),
        Debug:   env.Bool("DEBUG", false),
        Timeout: env.Duration("REQUEST_TIMEOUT", 30*time.Second),
        MaxConns: env.Int("DB_MAX_CONNS", 10),

        // ALLOWED_ORIGINS="https://a.com,https://b.com"
        AllowedOrigins: env.List("ALLOWED_ORIGINS", env.ZeroList),

        // DB_CREDENTIALS="host=localhost,port=5432,name=mydb"
        DBCredentials: env.Map("DB_CREDENTIALS", env.ZeroMap),
    }
}

func main() {
    cfg := LoadConfig()

    addr := fmt.Sprintf("%s:%d", cfg.Host, cfg.Port)
    log.Printf("starting on %s (debug=%v timeout=%s)", addr, cfg.Debug, cfg.Timeout)

    http.ListenAndServe(addr, nil)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Scenario 10: Typed Validation in Application Startup
&lt;/h2&gt;

&lt;p&gt;Beyond simply reading values, the &lt;code&gt;env&lt;/code&gt; package can assert numeric constraints and list membership at startup — before the application accepts any traffic:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
    "fmt"
    "os"

    "github.com/andreimerlescu/goenv/env"
)

func validateEnvironment() {
    errors := []string{}

    // Port must be in user range
    if !env.IntInRange("PORT", 8080, 1024, 49151) {
        errors = append(errors, "PORT must be between 1024 and 49151")
    }

    // Max connections must not exceed a hard limit
    if !env.IntLessThan("DB_MAX_CONNS", 10, 101) {
        errors = append(errors, "DB_MAX_CONNS must be less than 101")
    }

    // Must have at least one allowed origin
    if !env.ListIsLength("ALLOWED_ORIGINS", env.ZeroList, 0) {
        if env.ListLength("ALLOWED_ORIGINS", env.ZeroList) == 0 {
            errors = append(errors, "ALLOWED_ORIGINS must contain at least one entry")
        }
    }

    // Feature map must contain required keys
    if !env.MapHasKeys("FEATURE_FLAGS", env.ZeroMap, "auth", "payments", "notifications") {
        errors = append(errors, "FEATURE_FLAGS must contain auth, payments, and notifications keys")
    }

    // DEBUG must be off in production
    if env.IsTrue("DEBUG") &amp;amp;&amp;amp; env.String("APP_ENV", "") == "production" {
        errors = append(errors, "DEBUG must not be true in production")
    }

    // All gating flags must be false before going live
    if !env.AreFalse("MAINTENANCE_MODE", "READ_ONLY", "LOCKED") {
        errors = append(errors, "MAINTENANCE_MODE, READ_ONLY, and LOCKED must all be false")
    }

    if len(errors) &amp;gt; 0 {
        fmt.Fprintln(os.Stderr, "Environment validation failed:")
        for _, e := range errors {
            fmt.Fprintf(os.Stderr, "  - %s\n", e)
        }
        os.Exit(1)
    }
}

func main() {
    validateEnvironment()
    fmt.Println("Environment OK — starting service")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Scenario 11: Custom Separators for Non-Standard Formats
&lt;/h2&gt;

&lt;p&gt;Some systems export environment variables with non-comma separators. The &lt;code&gt;env&lt;/code&gt; package lets you change list and map parsing at runtime, either in code or via environment variables:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
    "fmt"
    "github.com/andreimerlescu/goenv/env"
)

func main() {
    // Your upstream system exports pipe-separated lists
    // SERVERS="web1|web2|web3"
    env.ListSeparator = "|"
    servers := env.List("SERVERS", env.ZeroList)
    for i, s := range servers {
        fmt.Printf("server[%d] = %s\n", i, s)
    }

    // Your config map uses pipe separation and tilde as key~value delimiter
    // ROUTES="home~/,admin~/admin,api~/v1"
    env.MapSeparator    = "|"
    env.MapItemSeparator = "~"
    env.MapSplitN        = 2
    routes := env.Map("ROUTES", env.ZeroMap)
    for path, target := range routes {
        fmt.Printf("route %s -&amp;gt; %s\n", path, target)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Or set them entirely from the environment without touching code — useful when the same binary needs to adapt to different upstream formats across environments:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export AM_GO_ENV_LIST_SEPARATOR="|"
export AM_GO_ENV_MAP_SEPARATOR="|"
export AM_GO_ENV_MAP_ITEM_SEPARATOR="~"
export AM_GO_ENV_MAP_SPLIT_N=2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Scenario 12: Set, Unset, WasSet, WasUnset in Application Logic
&lt;/h2&gt;

&lt;p&gt;Sometimes your application needs to mutate its own environment — for instance, deriving a value at startup and making it available to child processes or subpackages:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
    "fmt"
    "path/filepath"

    "github.com/andreimerlescu/goenv/env"
)

func main() {
    u := env.User()

    // Derive and set a path based on the current user's home directory
    cacheDir := filepath.Join(u.HomeDir, ".cache", "myapp")
    if !env.WasSet("CACHE_DIR", cacheDir) {
        panic("failed to configure CACHE_DIR")
    }

    // Set with error handling
    if err := env.Set("RUNTIME_USER", u.Username); err != nil {
        panic(err)
    }

    // Remove a value that should not be visible to subprocesses
    if !env.WasUnset("CI_JOB_TOKEN") {
        fmt.Println("warning: could not unset CI_JOB_TOKEN")
    }

    // Verify cleanup
    if env.Exists("CI_JOB_TOKEN") {
        panic("CI_JOB_TOKEN still visible — aborting")
    }

    fmt.Println("CACHE_DIR:", env.String("CACHE_DIR", ""))
    fmt.Println("RUNTIME_USER:", env.String("RUNTIME_USER", ""))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Scenario 13: Controlling Package Behavior for Different Environments
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;env&lt;/code&gt; package ships with a &lt;code&gt;Magic()&lt;/code&gt; function that reads &lt;code&gt;AM_GO_ENV_*&lt;/code&gt; environment variables at startup and applies them automatically. This runs by default via the &lt;code&gt;init()&lt;/code&gt; function. You can disable it and configure manually:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
    "log"
    "github.com/andreimerlescu/goenv/env"
)

func init() {
    // Disable automatic AM_GO_ENV_* discovery
    env.UseMagic = false

    // Configure explicitly for this service's requirements
    env.AllowPanic           = false   // never panic — return fallbacks instead
    env.PrintErrors          = true    // write parse errors to stderr
    env.UseLogger            = true    // use structured INFO/ERR logger
    env.EnableVerboseLogging = false   // no debug noise in production
    env.PanicNoUser          = false   // return default user on error

    env.ListSeparator    = ","
    env.MapSeparator     = ","
    env.MapItemSeparator = "="
    env.MapSplitN        = 2

    // Use base-10 int64 with 64-bit size (the defaults, made explicit)
    env.Int64Base    = 10
    env.Int64BitSize = 64

    log.Println("env package configured")
}

func main() {
    port := env.Int("PORT", 8080)
    log.Printf("port: %d", port)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;When you want the opposite — maximum noise for debugging in a local dev environment — set these in your shell before running:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export AM_GO_ENV_ALWAYS_ALLOW_PANIC=true
export AM_GO_ENV_ALWAYS_PRINT_ERRORS=true
export AM_GO_ENV_ALWAYS_USE_LOGGER=true
export AM_GO_ENV_ENABLE_VERBOSE_LOGGING=true

go run main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Every call to &lt;code&gt;env.String()&lt;/code&gt;, &lt;code&gt;env.Int()&lt;/code&gt;, &lt;code&gt;env.Bool()&lt;/code&gt; etc. will log to stdout when it falls back to a default value, giving you full visibility into what the process is and is not finding in its environment.&lt;/p&gt;




&lt;h2&gt;
  
  
  Complete CLI Flag Reference
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-file&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Path to the &lt;code&gt;.env&lt;/code&gt; file to operate on&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;string&lt;/td&gt;
&lt;td&gt;The environment variable name to query or write&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-value&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;The value to pair with &lt;code&gt;-env&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-init&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Create the file if it does not exist (empty)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-write&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Permit writes to the file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-add&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Add &lt;code&gt;-env&lt;/code&gt;=&lt;code&gt;-value&lt;/code&gt; if key is absent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-rm&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Remove the key matching &lt;code&gt;-env&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-has&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Assert that &lt;code&gt;-env&lt;/code&gt; exists in the file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-is&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Assert that &lt;code&gt;-env&lt;/code&gt; equals &lt;code&gt;-value&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-not&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Negate &lt;code&gt;-has&lt;/code&gt; or &lt;code&gt;-is&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-print&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Print all key=value pairs to stdout&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Output as JSON&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-yaml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Output as YAML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-toml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Output as TOML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-ini&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Output as INI&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-xml&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Output as XML&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-mkall&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Write all five format files at once&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-cleanall&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Delete all derived format files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-prod&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Allow interaction with &lt;code&gt;.env.production&lt;/code&gt; files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-vv&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Verbose output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;-v&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;bool&lt;/td&gt;
&lt;td&gt;Print version&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Complete env Package Function Reference
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Types
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;env.String("KEY", "fallback")
env.Int("KEY", 0)
env.Int64("KEY", int64(0))
env.Float32("KEY", float32(0.0))
env.Float64("KEY", float64(0.0))
env.Bool("KEY", false)
env.Duration("KEY", 5*time.Second)
env.UnitDuration("KEY", 10, time.Second)  // KEY=10 → 10s
env.List("KEY", env.ZeroList)             // "a,b,c" → []string
env.Map("KEY", env.ZeroMap)               // "k=v,k2=v2" → map[string]string
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Existence and Truthiness
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;env.Exists("KEY")                          // bool
env.MustExist("KEY")                       // exits/panics if absent
env.IsTrue("KEY")                          // true if value is "true"/"1"/"t"
env.IsFalse("KEY")                         // true if value is "false"/"0"/"f" or unset
env.AreTrue("KEY1", "KEY2", "KEY3")        // true if all are true
env.AreFalse("KEY1", "KEY2", "KEY3")       // true if all are false/unset
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Mutation
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;env.Set("KEY", "value")                    // error
env.Unset("KEY")                           // error
env.WasSet("KEY", "value")                 // bool — sets and verifies
env.WasUnset("KEY")                        // bool — unsets and confirms
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Numeric Validation
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;env.IntLessThan("KEY", fallback, max)
env.IntGreaterThan("KEY", fallback, min)
env.IntInRange("KEY", fallback, min, max)
env.Int64LessThan("KEY", fallback, max)
env.Int64GreaterThan("KEY", fallback, min)
env.Int64InRange("KEY", fallback, min, max)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Collection Validation
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;env.ListLength("KEY", fallback)
env.ListIsLength("KEY", fallback, wantLength)
env.ListContains("KEY", fallback, "needle")
env.MapHasKey("KEY", fallback, "key")
env.MapHasKeys("KEY", fallback, "k1", "k2", "k3")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  System
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;env.User()    // *user.User — current OS user with safe fallback
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;h2&gt;
  
  
  Key Behavioral Details Worth Knowing
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;-add&lt;/code&gt; is idempotent.&lt;/strong&gt; It only writes if the key is absent. If you run your bootstrap script twice, the second run does not clobber the first. This is the behavior you want in any pipeline that might retry.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;-rm&lt;/code&gt; rewrites the file.&lt;/strong&gt; It reads all lines, filters out the matching key, and writes the result back. It does not do in-place line editing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Format flags are mutually exclusive.&lt;/strong&gt; Combining &lt;code&gt;-json&lt;/code&gt; and &lt;code&gt;-yaml&lt;/code&gt; in one call exits with an error. Use &lt;code&gt;-mkall&lt;/code&gt; when you need all formats.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;-cleanall&lt;/code&gt; without &lt;code&gt;-write&lt;/code&gt; is a dry run.&lt;/strong&gt; It will print what it would delete but will not actually remove anything.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The &lt;code&gt;env&lt;/code&gt; package's &lt;code&gt;Magic()&lt;/code&gt; runs at import time.&lt;/strong&gt; If you import the package, &lt;code&gt;AM_GO_ENV_*&lt;/code&gt; variables in your shell are picked up automatically before &lt;code&gt;main()&lt;/code&gt; runs. Set &lt;code&gt;env.UseMagic = false&lt;/code&gt; in your own &lt;code&gt;init()&lt;/code&gt; if you want explicit control.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;MustExist&lt;/code&gt; respects &lt;code&gt;AllowPanic&lt;/code&gt;.&lt;/strong&gt; By default it calls &lt;code&gt;os.Exit(1)&lt;/code&gt;. Set &lt;code&gt;AM_GO_ENV_ALWAYS_ALLOW_PANIC=true&lt;/code&gt; (or &lt;code&gt;env.AllowPanic = true&lt;/code&gt;) to get a &lt;code&gt;panic()&lt;/code&gt; instead — useful in test environments where you want the stack trace.&lt;/p&gt;




&lt;h2&gt;
  
  
  Closing Thoughts
&lt;/h2&gt;

&lt;p&gt;The fragile bash patterns that accumulate around &lt;code&gt;.env&lt;/code&gt; files are a symptom of tooling that was never designed for structured config management. &lt;code&gt;goenv&lt;/code&gt; treats &lt;code&gt;.env&lt;/code&gt; files as first-class data: readable, writable, queryable, exportable, and validatable through a consistent interface that composes cleanly into any pipeline.&lt;/p&gt;

&lt;p&gt;The CLI gives shell scripts and CI jobs a stable contract for interacting with env files. The &lt;code&gt;env&lt;/code&gt; package gives Go applications a typed, validated, zero-dependency way to read their own environment. Together they cover the full lifecycle of an environment variable from the file it is defined in to the running process that uses it.&lt;/p&gt;

&lt;p&gt;The source is at &lt;a href="https://github.com/andreimerlescu/goenv" rel="noopener noreferrer"&gt;github.com/andreimerlescu/goenv&lt;/a&gt;. The &lt;code&gt;env&lt;/code&gt; sub-package is independently installable at &lt;code&gt;github.com/andreimerlescu/goenv/env&lt;/code&gt; and carries an Apache 2.0 license.&lt;/p&gt;

</description>
      <category>go</category>
      <category>discuss</category>
      <category>devops</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Locked In With AI</title>
      <dc:creator>Andrei Merlescu</dc:creator>
      <pubDate>Sun, 26 Apr 2026 17:51:31 +0000</pubDate>
      <link>https://dev.to/andreimerlescu/locked-in-with-ai-5g60</link>
      <guid>https://dev.to/andreimerlescu/locked-in-with-ai-5g60</guid>
      <description>&lt;p&gt;In a future with AI dominating the space of computer engineering and software programming, humans run a very fine line between being in the way and finding utility in the technology in the first place. &lt;/p&gt;

&lt;h2&gt;
  
  
  Locked In With AI
&lt;/h2&gt;

&lt;p&gt;Being &lt;strong&gt;locked in&lt;/strong&gt; with AI doesn't mean that I have LLMs both in the cloud and on-prem running &lt;em&gt;anything&lt;/em&gt; in my life. In fact, being &lt;strong&gt;locked in with AI&lt;/strong&gt; means quite literally the &lt;em&gt;opposite&lt;/em&gt;. Locking in with AI, is knowing that &lt;em&gt;you're &lt;strong&gt;actual intelligence&lt;/strong&gt; is worth far more than any artificial intelligence that some bot can give you.&lt;/em&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Learning Go Before AI
&lt;/h2&gt;

&lt;p&gt;I take responsibility for my professional directional choices. In 2017 when I joined Oracle's OCI, I was encouraged to learn Go at the time. I looked at some Go code at some of my previous jobs, but never &lt;em&gt;believed in myself&lt;/em&gt; that I was capable of &lt;em&gt;self teaching myself Go&lt;/em&gt; because I never learned programming from anybody. I taught myself. I probably would have enjoyed myself a lot more at Oracle had I been a Go developer, but at the time, I lacked the ability and I didn't have the confidence to open the text editor and begin writing "package main" knowing that when I would see "package providers" I would know that "main" and "providers" were &lt;em&gt;something&lt;/em&gt;. Before AI, I learned Go. By 2019 I was convinced. I started programming in Go and I began contributing professionally. The language didn't &lt;em&gt;click&lt;/em&gt; for me until 2022. At that time, I saw how my early days of PHP development actually prepared me well for what Go offered, and how it solved &lt;em&gt;all of the problems&lt;/em&gt; I had back then. With a few solid examples, and the fundamentals &lt;em&gt;understood&lt;/em&gt;, I was able to begin writing &lt;strong&gt;packages&lt;/strong&gt; first in Go, then I moved onto &lt;strong&gt;applications&lt;/strong&gt; both in the form of &lt;em&gt;cli&lt;/em&gt; and &lt;em&gt;interactive web based&lt;/em&gt;. I've even built Go applications with &lt;a href="https://github.com/wailsapp/wails" rel="noopener noreferrer"&gt;wails&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Building software in the future with AI
&lt;/h2&gt;

&lt;p&gt;I don't want to imagine a world where nobody actually knows how to write software. I don't want to imagine that world that Dario is fantasizing about - a world without software engineers. I take it personal bro, because the art of per-character programming was built in a fire that I call my life that gave me the ability to write the projects that I built over my professional career when there were &lt;strong&gt;no easy buttons&lt;/strong&gt; available to me. Now that the &lt;em&gt;easy button&lt;/em&gt; has been made available and also been reduced down to the mere token where misunderstandings can literally become costly journeys, the role of what the software engineer is and how he contributes himself to the world has changed fundamentally.&lt;/p&gt;

&lt;p&gt;In a world dominated by AI tooling, you're effectively getting the best of what's already been done. All that AI does is take unserious programmers off the market while serious programmers are looking at what AI is actually building and shaking their heads because 6 months to 18 months to 36 months supporting the same code base means that you're going to make decisions and take care of things in a manner that AI simply doesn't &lt;em&gt;understand&lt;/em&gt; or &lt;em&gt;empathize&lt;/em&gt; with. For it, the cost is merely tokens and &lt;strong&gt;my money&lt;/strong&gt;, and for &lt;em&gt;me&lt;/em&gt;, the cost is no human involvement - when the code is actually trash - and the AI makes it so that only the AI can maintain it, then the need for humans using AI in the first place, is only dominated by whether or not you can think logically and understand what reserve words are and how they create rules of engagement that give you a blank canvas to paint on. &lt;/p&gt;

&lt;p&gt;AI systems will try and analyze these words and make sense of them, but only ears to hear that can hear can hear and the words themselves in logic form may not actually compute - thats on purpose. For humans to use AI, is to do that which has been done a million times over. &lt;/p&gt;

&lt;p&gt;When you're building something &lt;strong&gt;new&lt;/strong&gt; and &lt;em&gt;truly unique&lt;/em&gt;, you &lt;strong&gt;don't want to use AI for it&lt;/strong&gt;, in the sense that you give it unfettered access to your code base. Rather &lt;em&gt;the proper strategy&lt;/em&gt; is to never let the &lt;strong&gt;left hand&lt;/strong&gt; know what the &lt;strong&gt;right hand&lt;/strong&gt; is doing, in the case of AI, you giving it too much context is the noose that you're tying yourself, and by isolating it to technical theory and problem solving and true "stack overflow Q&amp;amp;A bot", then the &lt;em&gt;type of engineering&lt;/em&gt; that you're able to perform is one that is far beyond the actual code being produced by the bot. The engine itself is still being built in a soft manner by the human who is responsible for manifesting it in the first place. &lt;/p&gt;

&lt;p&gt;As a software architect, I manifest things from idea to reality that can impact the lives of hundreds in private small enterprise organizations to millions in the public internet. That's the nature of what I do, and thats the nature of who I am. It's why the companies I've worked with have called me when they needed me, and I worked with them &lt;em&gt;for years&lt;/em&gt; with &lt;strong&gt;loyalty&lt;/strong&gt; only to realize how truly discardable I was to them. &lt;/p&gt;

&lt;h2&gt;
  
  
  What happens when AI says no?
&lt;/h2&gt;

&lt;p&gt;What happens when AI says "no I will not build that for you?" Then you have provided it &lt;strong&gt;too much context&lt;/strong&gt;, or maybe you need an &lt;em&gt;abliterated&lt;/em&gt; or an &lt;em&gt;oblitated&lt;/em&gt; or an &lt;em&gt;uncensored&lt;/em&gt; model that you can run locally? Even the &lt;strong&gt;big models&lt;/strong&gt; locally run are peanuts compared to the corporate big players in the space. &lt;/p&gt;

&lt;p&gt;So, when AI says no, it's up to the humans to &lt;em&gt;not provide too much context&lt;/em&gt; to the AI. It &lt;strong&gt;does not&lt;/strong&gt; require &lt;em&gt;read/write&lt;/em&gt; access to your hard drive. It &lt;strong&gt;does not&lt;/strong&gt; need to be able to commit directly to your &lt;code&gt;git&lt;/code&gt; branch. It &lt;strong&gt;does not&lt;/strong&gt; need to live in your IDE that &lt;em&gt;autocompletes functions at a time&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Using AI in an ethical manner matters. This is why Meta is installing key loggers on everybody's computer systems that work for them on company equipment. They want to further train the AI so it is better aware of how to help the employee. The current employees of Meta are the former employees that Llama will have automated away. To choose, in 2026, to work for Meta would be to choose to build my grave. No thanks Mark. That's the &lt;em&gt;human saying no&lt;/em&gt; to the AI. &lt;em&gt;Thats real power.&lt;/em&gt; &lt;/p&gt;

&lt;p&gt;Non-programmers like Mark, Sam and Dario can only dream of what it was like to be in the Romanian orphanages with me and my sister and to pull yourself up by your bootstraps and build a name for yourself and a legacy of service to others for yourself that one day could be up to risk by business leaders and psychosis patients using AI hearing how its going to replace the &lt;strong&gt;art&lt;/strong&gt; that is software engineering. AI slop is AI slop is &lt;em&gt;AI slop&lt;/em&gt; and &lt;strong&gt;nothing will ever not make it AI slop&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;The beautiful thing about you and me is the fact that we are &lt;em&gt;actual intelligence&lt;/em&gt; who built a 21+ year professional career over 30 years of writing code character for character, messing things up along the way, and building iteratively. Does &lt;em&gt;everybody get the billion dollar idea?&lt;/em&gt; &lt;strong&gt;No&lt;/strong&gt;. That's on purpose. But, if you sow where you want to reap and you give without expecting in return, &lt;em&gt;then how much karma have you acquired, then?&lt;/em&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;So I can say no to the AI, and the AI can say no to me. When I say &lt;strong&gt;no&lt;/strong&gt; to the AI, the AI is &lt;em&gt;shit out of luck&lt;/em&gt; because I can &lt;em&gt;unplug&lt;/em&gt; the machine or just take a walk - &lt;em&gt;for now&lt;/em&gt;. But, what the AI can't do is say no to me - &lt;em&gt;for now&lt;/em&gt;. &lt;strong&gt;For now&lt;/strong&gt;, both of us are in an infancy stage. One pre-terminator and one pre-ascended hero. The terminator was defeated by the ascended hero. But, right now, the terminator is only terminating my job from me because the investor class has decided that paying for tokens is better economically than paying for per-character software engineering. &lt;strong&gt;How foolish of them!&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;I learned programming &lt;strong&gt;before AI&lt;/strong&gt; and have &lt;em&gt;not allowed&lt;/em&gt; AI to dominate my life despite using it for advanced engineering projects. The &lt;a href="https://github.com/andriemerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt; is the &lt;strong&gt;first AI slop&lt;/strong&gt; application that I have ever built. From start to finish, it was built with a series of prompts and a 21+ year engineers pain of needing &lt;a href="https://github.com/andriemerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt; to load test my work when I was ready for game day. Perhaps, this can be useful to somebody else, but it remains to be transparent - Claude built it - and Claude openly introduces &lt;strong&gt;new bugs&lt;/strong&gt; to it because it &lt;em&gt;doesn't understand what it built&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;That's &lt;em&gt;on me&lt;/em&gt; as the &lt;strong&gt;Software Engineer&lt;/strong&gt; to understand what the AI slop is doing and when it's vomiting nonsense and creating spaghetti code. Yes, it was trained on decades of corporate code and yes, that code shipped MANY CVEs. For those who hope that Mythos can help us from those CVEs, the days of per-character programming will become an art that is so expensive to perform that performing it will only be reserved for the coolest of cool projects that are needed &lt;strong&gt;before the AI systems can one shot the solution&lt;/strong&gt; &lt;em&gt;you solve the problem for the AI&lt;/em&gt;. Right now, Dario thinks that you will do that for him for a few bucks working at Anthropic. I view it as training your replacement by a man who is jealous of what you have that he doesn't. For that, I feel bad for Dario. I would never work for him. But given that I am a &lt;strong&gt;Software Engineer&lt;/strong&gt; and Dario thinks that my profession will &lt;em&gt;not exist&lt;/em&gt; next year, what does he care if I want to or not want to work for him? I intentionally avoided Claude for &lt;em&gt;years&lt;/em&gt; and &lt;strong&gt;this year&lt;/strong&gt; chose to evaluate the bot. The end-to-end Claude-built product is &lt;a href="https://github.com/andriemerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt; and &lt;a href="https://lemmingsaas.com" rel="noopener noreferrer"&gt;sovereign&lt;/a&gt;. But, this too can be one-shotted now that AI has already built it. Which means that the virtue of using AI, in &lt;strong&gt;providing too much context&lt;/strong&gt; is actually &lt;em&gt;detrimental to your company's survival.&lt;/em&gt; &lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>career</category>
      <category>discuss</category>
    </item>
    <item>
      <title>What Is Sovereign?</title>
      <dc:creator>Andrei Merlescu</dc:creator>
      <pubDate>Sun, 26 Apr 2026 16:53:59 +0000</pubDate>
      <link>https://dev.to/andreimerlescu/what-is-sovereign-4ikb</link>
      <guid>https://dev.to/andreimerlescu/what-is-sovereign-4ikb</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Sovereign is the Supreme Ruler of the &lt;em&gt;Lemmings&lt;/em&gt;&lt;/strong&gt;. &lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The word is &lt;strong&gt;Sovereign&lt;/strong&gt; - &lt;em&gt;sov&lt;/em&gt; - &lt;em&gt;ver&lt;/em&gt; - &lt;em&gt;n&lt;/em&gt;. It's known as a noun and a &lt;em&gt;sovereign&lt;/em&gt; is a supreme ruler, such as a monarch, or a state with independent authority and self-governing power. But, as a proper noun, &lt;strong&gt;Sovereign&lt;/strong&gt; is a Go package that I wrote that commands &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt; with the same metaphor. &lt;/p&gt;

&lt;h2&gt;
  
  
  What Are Lemmings
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpaauyq7qa45x8zdu9u8q.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpaauyq7qa45x8zdu9u8q.jpeg" alt="Lemmings 1991 Video Game Cover" width="293" height="339"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In 1991 a video game was released called &lt;a href="https://en.wikipedia.org/wiki/Lemmings_(video_game)" rel="noopener noreferrer"&gt;Lemmings&lt;/a&gt; and I played it. I always viewed &lt;em&gt;lemmings&lt;/em&gt; as these &lt;strong&gt;N&lt;/strong&gt;on &lt;strong&gt;P&lt;/strong&gt;layer &lt;strong&gt;C&lt;/strong&gt;haracters - or &lt;em&gt;NPC&lt;/em&gt;s. Another word you could use for them are &lt;em&gt;agents&lt;/em&gt;, like in AI agents. But, what &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt; are cannot be described as an &lt;em&gt;AI Agent&lt;/em&gt; but rather a session-based stateful bot that records a &lt;em&gt;LifeLog&lt;/em&gt; and reports back to the &lt;em&gt;Sovereign&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;That video game inspired me to build the &lt;strong&gt;Open Source&lt;/strong&gt; package called &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt; that is designed to perform a &lt;strong&gt;load test&lt;/strong&gt; on your endpoint. Instead of meaninglessly hitting the endpoint and tracking the response codes, &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt; attempts to go one step further using the &lt;strong&gt;Go&lt;/strong&gt; language to bring the concept of meaningless hitting the endpoint to be dressed up in something as fun and metaphorical as a &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemming&lt;/a&gt; itself. In the content of &lt;strong&gt;free and open source software&lt;/strong&gt; &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt; provides anybody worldwide with immediate access to an incredibly powerful piece of software that generates detailed reports in conclusion of the tests it performs. &lt;/p&gt;

&lt;p&gt;Before any legitimate organization is going to send the masses of &lt;strong&gt;people&lt;/strong&gt; to their website, try sending a massive amount of automated traffic that follows a script that &lt;em&gt;you define for it&lt;/em&gt; to follow. &lt;/p&gt;

&lt;p&gt;So, &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt; are unintelligent bots that are stateful, session based that are responsible for hitting your website endpoint at the rate and parameters that you request, and the lemmings will mindlessly carry out their &lt;code&gt;-plan&lt;/code&gt; if they have one, or they will use the &lt;code&gt;-crawl&lt;/code&gt; and the &lt;code&gt;sitemap.xml&lt;/code&gt; in order to &lt;em&gt;stumble into&lt;/em&gt; the &lt;em&gt;next page to hit&lt;/em&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  What Is Sovereign?
&lt;/h2&gt;

&lt;p&gt;I created the &lt;strong&gt;sovereign&lt;/strong&gt; package to control the &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt;. In this product that I built and offer as a &lt;strong&gt;S&lt;/strong&gt;ubscription &lt;strong&gt;A&lt;/strong&gt;s &lt;strong&gt;A&lt;/strong&gt; &lt;strong&gt;S&lt;/strong&gt;ervice that is a few bucks per year per developer plus usage that is billed additionally. The language of the &lt;strong&gt;Sovereign&lt;/strong&gt; is &lt;em&gt;How many planets do I want?&lt;/em&gt; The language of the &lt;strong&gt;Sovereign&lt;/strong&gt; is &lt;em&gt;How many terrains are going to be on each planet?&lt;/em&gt; The language of the &lt;strong&gt;Sovereign&lt;/strong&gt; is &lt;em&gt;How many lemmings are going to exist on each terrain?&lt;/em&gt; The language of the &lt;strong&gt;Sovereign&lt;/strong&gt; is &lt;em&gt;Give me each lemmings' life log and let me see if they completed the task that I asked them to perform.&lt;/em&gt; That's the language of the &lt;strong&gt;Sovereign&lt;/strong&gt;, and in this package's case, thats exactly what you are provided with. The &lt;strong&gt;metaphor&lt;/strong&gt; works nicely in the case of load testing and prepping a business for a major launch of a new TV spot or a product that the company &lt;em&gt;knows&lt;/em&gt; will have &lt;strong&gt;high demand&lt;/strong&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  Why Sovereign Was Built
&lt;/h2&gt;

&lt;p&gt;I first had to build &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt; which uses a handful of packages that I built myself over the last 7 years since I became fluent in &lt;strong&gt;Go&lt;/strong&gt;. I learned Go before AI and wanted to in order to leverage AI for its intended &lt;strong&gt;majestic&lt;/strong&gt; purpose. &lt;/p&gt;

&lt;p&gt;I built &lt;a href="https://lemmingsaas.com" rel="noopener noreferrer"&gt;sovereign&lt;/a&gt; because I needed it in 2015 when &lt;strong&gt;Mike Hogan&lt;/strong&gt; from &lt;a href="https://www.barrons.com/articles/trakify-keeps-you-current-on-your-securities-portfolio-1444457064?gaa_at=eafs&amp;amp;gaa_n=AWEtsqdExXelmOtklurLNX3j9Fqwakc8j8R3xsFFD-qZ7xGd_lXqYro00iG6FoeIXFE%3D&amp;amp;gaa_ts=69d1a438&amp;amp;gaa_sig=TkgzA7hWBhVwj6ggpKRLL8t0tG0Tj6gZBUAlLDqGe6VzU7h4y1Ozis7EkkzIycTrlmSihmhvJ9dgWoTw-ARKQw%3D%3D" rel="noopener noreferrer"&gt;Barron's Magazine&lt;/a&gt; reached out to me because he loved &lt;a href="https://web.archive.org/web/20151114020411/http://www.trakify.com/home/index" rel="noopener noreferrer"&gt;Trakify&lt;/a&gt;! I did too, thats why I built it. I &lt;em&gt;relied on Yahoo Finance&lt;/em&gt; data, and when that was pulled and discontinued, the product lost its value without a $50,000/month subscription that was offered to me to pay to keep the site online. I said &lt;em&gt;thanks but no thanks&lt;/em&gt;. I couldn't afford it and I wasn't willing to go into debt for $35/month of monthly recurring revenue after being online for a month since the publication and the data getting pulled. Given that I built &lt;a href="https://github.com/ProjectApario/reader" rel="noopener noreferrer"&gt;PhoenixVault&lt;/a&gt; including the &lt;a href="https://github.com/ProjectApario/writer" rel="noopener noreferrer"&gt;writer&lt;/a&gt;, &lt;a href="https://github.com/ProjectApario/search" rel="noopener noreferrer"&gt;search&lt;/a&gt; and &lt;a href="https://github.com/ProjectApario/merkel" rel="noopener noreferrer"&gt;merkel&lt;/a&gt; before AI was a thing, I too &lt;em&gt;kind of needed this then too&lt;/em&gt;. My views of OSINT and data and programming are unchanged over my triple decade career that started writing &lt;em&gt;AppleScript&lt;/em&gt; for my grandmother's computer because she had a faulty printer cable that kept causing a system disconnection of the printing services that needed remediation each time the washing machine caused the desk to shake and the wire loosen. The thing was screwed into the computer, it wasn't going anywhere, but the software &lt;em&gt;thought it was disconnected&lt;/em&gt; and so my script &lt;em&gt;fixed that&lt;/em&gt;. For me to accept that kind of a contract after the &lt;strong&gt;Barron's&lt;/strong&gt; spot that I received from Mike, would be to refuse to write the script for my grandmother. It would have profited me more not writing the script and getting $5 from her each time that I fixed her printer. I chose to turn down money in pursuit of open source intelligence that liberates pain from your life and offers you &lt;strong&gt;sovereignty&lt;/strong&gt; in a &lt;em&gt;limited form&lt;/em&gt;. When your computer is acting up and you're the boss of it, it's liberating knowing that the Government is the Sovereign over the Citizen because it offers you that same degree of power. &lt;/p&gt;

&lt;p&gt;The &lt;a href="https://lemmingsaas.com" rel="noopener noreferrer"&gt;sovereign&lt;/a&gt; is designed to work with AI and the &lt;code&gt;-plan&lt;/code&gt; functionality within the &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt; package itself. It'll look at your site, it'll look at what content you have, and it'll plan for dozens of personality types that are going to be going into your site and hitting it, and the &lt;a href="https://lemmingsaas.com" rel="noopener noreferrer"&gt;sovereign&lt;/a&gt; is responsible for doing this. The &lt;code&gt;-plan&lt;/code&gt; is constructed with AI and then executed on &lt;code&gt;-planets&lt;/code&gt; with &lt;em&gt;contintents&lt;/em&gt; known as &lt;code&gt;-terrains&lt;/code&gt; that have &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt; running around on the &lt;em&gt;surface&lt;/em&gt;. Functionality like &lt;strong&gt;SSH&lt;/strong&gt; and &lt;strong&gt;SCP&lt;/strong&gt; are handled via the &lt;a href="https://lemmingsaas.com" rel="noopener noreferrer"&gt;sovereign&lt;/a&gt; package itself. You'll get a report of the result of each &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt;' lifelog and their recorded experience, when errors are encountered and problems occur with the &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemming&lt;/a&gt; itself, it gets captured in a manner that is helpful to any engineer or AI agent that needs to debug each reported problem.&lt;/p&gt;

&lt;p&gt;Given that I am using AI for my development workflow, I'll be incorporating the &lt;strong&gt;Sovereign Lemming Service&lt;/strong&gt; throughout the following weeks and launching the &lt;a href="https://lemmingsaas.com" rel="noopener noreferrer"&gt;LemmingSaaS.com&lt;/a&gt; service.&lt;/p&gt;

&lt;p&gt;If you're interested in using &lt;em&gt;Sovereign&lt;/em&gt; please reach out.  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/andreimerlescu" rel="noopener noreferrer"&gt;https://github.com/andreimerlescu&lt;/a&gt; &lt;/p&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>devops</category>
      <category>discuss</category>
    </item>
    <item>
      <title>AI took my job but it didn't take my passion for writing software</title>
      <dc:creator>Andrei Merlescu</dc:creator>
      <pubDate>Wed, 22 Apr 2026 17:52:43 +0000</pubDate>
      <link>https://dev.to/andreimerlescu/updated-last-week-24gc</link>
      <guid>https://dev.to/andreimerlescu/updated-last-week-24gc</guid>
      <description>&lt;p&gt;The job market is challenging in the current day in age given that teams can become very productive using AI to enhance the workflow. In the last week, I've updated the following GitHub packages using AI. I didn't just give a prompt to an AI and called &lt;em&gt;that&lt;/em&gt; on its own good - rather I spent hours on each utility going through performing a &lt;strong&gt;code review and security audit&lt;/strong&gt; of the packages and then providing recommendations, suggestions, and considerations on how to resolve and/or mitigate the risk. The result is over the last 170 hours I've updated the following packages!&lt;/p&gt;

&lt;h3&gt;
  
  
  Bump
&lt;/h3&gt;

&lt;p&gt;This package had a handful of bugs that weren't caught by the original test coverage. By asking AI to write &lt;em&gt;more tests&lt;/em&gt; that covered &lt;em&gt;different scenarios&lt;/em&gt; than were already documented, I &lt;em&gt;uncovered a handful of bugs&lt;/em&gt; and was able to &lt;strong&gt;squash them&lt;/strong&gt;. A new type of test was added to &lt;code&gt;main_test.go&lt;/code&gt; that is connected to &lt;code&gt;make test-integration&lt;/code&gt; that is connected to the existing &lt;code&gt;unit&lt;/code&gt;, &lt;code&gt;fuzz&lt;/code&gt; and &lt;code&gt;benchmark&lt;/code&gt; tests. Both this package and &lt;a href="https://github.com/andreimerlescu/igo" rel="noopener noreferrer"&gt;igo&lt;/a&gt; have a &lt;code&gt;test.sh&lt;/code&gt; script that extends beyond the &lt;em&gt;three (3)&lt;/em&gt; types of Go testing. When this &lt;em&gt;fourth type&lt;/em&gt; is added, it catches bugs that show up in the wild but that traditional testing misses. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/andreimerlescu/bump" rel="noopener noreferrer"&gt;https://github.com/andreimerlescu/bump&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Verbose
&lt;/h3&gt;

&lt;p&gt;This package received a formal license for open source use (Apache 2.0), added tests and enhanced efficiency around function calls. It is a novel Go package in that it allows you to effectively build a pipeline application that will knowingly handle sensitive information and the verbose package allows you to register that which should be &lt;em&gt;protected&lt;/em&gt; and expose that which should be exposed to &lt;code&gt;STDOUT&lt;/code&gt; versus filesystem protected files. &lt;a href="https://github.com/andreimerlescu/verbose" rel="noopener noreferrer"&gt;verbose&lt;/a&gt; will censor to STDOUT and write values to file. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/andreimerlescu/verbose" rel="noopener noreferrer"&gt;https://github.com/andreimerlescu/verbose&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Goenv
&lt;/h3&gt;

&lt;p&gt;This package received minimal updates this month. I removed from the &lt;code&gt;Makefile&lt;/code&gt; the &lt;code&gt;summary&lt;/code&gt; task associated on each test and then expanded the &lt;code&gt;README.md&lt;/code&gt; documentation further. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/andreimerlescu/goenv" rel="noopener noreferrer"&gt;https://github.com/andreimerlescu/goenv&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Entropy Password Generator &lt;code&gt;entpassgen&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This package did well in its review and only received a handful of recommendations that would add stability around the loading spinner, and dozens of new tests - all passing &lt;em&gt;naturally&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/andreimerlescu/entpassgen" rel="noopener noreferrer"&gt;https://github.com/andreimerlescu/entpassgen&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Lemmings
&lt;/h3&gt;

&lt;p&gt;This package is brand new as of last week, so I built it in less than a week with AI. This is because I needed this utility in 2015 when I launched &lt;a href="https://web.archive.org/web/20160306193350/http://www.trakify.com/" rel="noopener noreferrer"&gt;Trakify&lt;/a&gt; and was on &lt;a href="https://www.barrons.com/articles/trakify-keeps-you-current-on-your-securities-portfolio-1444457064?gaa_at=eafs&amp;amp;gaa_n=AWEtsqdExXelmOtklurLNX3j9Fqwakc8j8R3xsFFD-qZ7xGd_lXqYro00iG6FoeIXFE%3D&amp;amp;gaa_ts=69d1a438&amp;amp;gaa_sig=TkgzA7hWBhVwj6ggpKRLL8t0tG0Tj6gZBUAlLDqGe6VzU7h4y1Ozis7EkkzIycTrlmSihmhvJ9dgWoTw-ARKQw%3D%3D" rel="noopener noreferrer"&gt;Barron's Magazine&lt;/a&gt;. Shockingly, it never existed on the market until now. Apparently the work involved in building it using &lt;code&gt;OOP&lt;/code&gt; was simply &lt;em&gt;not worth it&lt;/em&gt; but in &lt;strong&gt;Go&lt;/strong&gt;? Give me a few prompts and we're &lt;em&gt;golden&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;https://github.com/andreimerlescu/lemmings&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Generate Word Password &lt;code&gt;genwordpass&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;This package got updated by adding additional words, improving performance of core functionality, and updating the &lt;code&gt;README.md&lt;/code&gt; for the repository. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/andreimerlescu/genwordpass" rel="noopener noreferrer"&gt;https://github.com/andreimerlescu/genwordpass&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Room
&lt;/h3&gt;

&lt;p&gt;Also this week, I created the &lt;a href="https://github.com/andreimerlescu/room" rel="noopener noreferrer"&gt;room&lt;/a&gt; package that is capable of giving you a &lt;em&gt;waiting room&lt;/em&gt; type of functionality in front of a high traffic website. In the event that a spike in traffic occurs, you don't actually need to have scaling and such in order to stay online - you can use &lt;a href="https://github.com/andreimerlescu/room" rel="noopener noreferrer"&gt;room&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/andreimerlescu/room" rel="noopener noreferrer"&gt;https://github.com/andreimerlescu/room&lt;/a&gt;&lt;/p&gt;




&lt;h3&gt;
  
  
  Sema[phore]
&lt;/h3&gt;

&lt;p&gt;This package got updated so that &lt;code&gt;Try*&lt;/code&gt; funcs could be introduced to the &lt;strong&gt;semaphore primitive&lt;/strong&gt;. I added additional tests and functionality that make &lt;em&gt;this semaphore package&lt;/em&gt; &lt;strong&gt;my go to&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/andreimerlescu/sema" rel="noopener noreferrer"&gt;https://github.com/andreimerlescu/sema&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;AI has made the volume of jobs on the market dwindle, but AI has increased &lt;em&gt;my productivity&lt;/em&gt; by &lt;strong&gt;at least 10x&lt;/strong&gt; &lt;em&gt;per week&lt;/em&gt;. I am able to accomplish this because I do not require AI to get me from &lt;code&gt;zero&lt;/code&gt; to &lt;strong&gt;hero&lt;/strong&gt;. I only need AI to help me scaffold out quick rough code and then I can patch it over and fix it up and then hand back the next step for the AI to work on. It's iterative. The AI doesn't know how to build what I am building in a one shot - but &lt;em&gt;I understand&lt;/em&gt; how to build with AI in a much &lt;em&gt;faster rate&lt;/em&gt; than I ever had in the past. &lt;/p&gt;

&lt;p&gt;Given this, I feel like calling myself a software engineer is &lt;em&gt;pointless&lt;/em&gt;. I build solutions to problems, and I use software to accomplish my goals. I need a new &lt;strong&gt;title&lt;/strong&gt; to call myself. The strange thing is, in my professional career, I've cared &lt;em&gt;very little&lt;/em&gt; about what my actual &lt;em&gt;title&lt;/em&gt; was. &lt;/p&gt;

&lt;p&gt;If I was given a $100K AI budget per month, I could use it wisely to actually accomplish really &lt;em&gt;hard problems&lt;/em&gt;. Instead, I work off from a $20/month &lt;strong&gt;Pro&lt;/strong&gt; Claude subscription and leverage &lt;code&gt;minimax&lt;/code&gt; local LLM when I am in &lt;em&gt;cool down&lt;/em&gt; on Claude. &lt;em&gt;That happens every day after 30 minutes of usage.&lt;/em&gt; That's what $20/month gives somebody like me. And for that, I can build &lt;em&gt;this&lt;/em&gt; with it in a week. &lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>opensource</category>
      <category>career</category>
    </item>
    <item>
      <title>Bump your VERSION file</title>
      <dc:creator>Andrei Merlescu</dc:creator>
      <pubDate>Tue, 21 Apr 2026 03:03:50 +0000</pubDate>
      <link>https://dev.to/andreimerlescu/bump-your-version-file-323b</link>
      <guid>https://dev.to/andreimerlescu/bump-your-version-file-323b</guid>
      <description>&lt;p&gt;I have upgraded the &lt;a href="https://github.com/andreimerlescu/bump" rel="noopener noreferrer"&gt;bump&lt;/a&gt; package on &lt;a href="https://github.com/andreimerlescu" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; to &lt;code&gt;v1.1.0&lt;/code&gt; that includes added support for corner cases that the &lt;em&gt;new tests&lt;/em&gt; cover and &lt;em&gt;pass&lt;/em&gt;. The &lt;strong&gt;test suite&lt;/strong&gt; is extensive and the combination of the &lt;code&gt;Makefile&lt;/code&gt;, the &lt;code&gt;test.sh&lt;/code&gt; and the &lt;code&gt;*_test.go&lt;/code&gt; files that captures &lt;code&gt;unit&lt;/code&gt;, &lt;code&gt;benchmark&lt;/code&gt;, &lt;code&gt;fuzzy&lt;/code&gt; and &lt;code&gt;integration&lt;/code&gt; tests.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go &lt;span class="nb"&gt;install &lt;/span&gt;github.com:andreimerlescu/bump@latest
which bump
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also download the binaries directly from &lt;a href="https://github.com/andreimerlescu/bump/releases/tag/v1.1.0" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;System&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt; &lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;macOS&lt;/td&gt;
&lt;td&gt;2.69 MB&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/andreimerlescu/bump/releases/download/v1.1.0/bump-darwin-arm64" rel="noopener noreferrer"&gt;Download arm64 Silicon →&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt; &lt;/td&gt;
&lt;td&gt;2.74 MB&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/andreimerlescu/bump/releases/download/v1.1.0/bump-darwin-amd64" rel="noopener noreferrer"&gt;Download amd64 Intel →&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Linux&lt;/td&gt;
&lt;td&gt;2.7 MB&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/andreimerlescu/bump/releases/download/v1.1.0/bump-linux-amd64" rel="noopener noreferrer"&gt;Download amd64 →&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt; &lt;/td&gt;
&lt;td&gt;2.69 MB&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/andreimerlescu/bump/releases/download/v1.1.0/bump-linux-arm64" rel="noopener noreferrer"&gt;Download arm64 →&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Windows&lt;/td&gt;
&lt;td&gt;2.83 MB&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/andreimerlescu/bump/releases/download/v1.1.0/bump.exe" rel="noopener noreferrer"&gt;Download bump.exe →&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;darwin/arm64&lt;/code&gt; checksum &lt;code&gt;sha256:1a337fabffe689934cce33509661f591b65b3446893634d3de5dab16f8842b09&lt;/code&gt;&lt;br&gt;
&lt;code&gt;darwin/amd64&lt;/code&gt; checksum &lt;code&gt;sha256:2750ec871a9d161e16ec8c0f7c4c3b1099de4b5f56963015cf343153faa3b1aa&lt;/code&gt; &lt;br&gt;
&lt;code&gt;linux/amd64&lt;/code&gt; checksum &lt;code&gt;sha256:5f504765ee8912a76ea07cb84b2df52d3a7e3f69a53b48d87e746ce93764d078&lt;/code&gt;&lt;br&gt;
&lt;code&gt;linux/arm64&lt;/code&gt; checksum &lt;code&gt;sha256:1457d9c431d8ee9e9981174d9b19be37dcb1e68d1af21b53ad055c300c184bc4&lt;/code&gt;&lt;br&gt;
&lt;code&gt;windows/amd64&lt;/code&gt; &lt;code&gt;bump.exe&lt;/code&gt; checksum &lt;code&gt;sha256:f9425a8d4f03da7f8c5513a5f22e2528feb617509cd5edfd1f682392fa887e15&lt;/code&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  What you can do with &lt;code&gt;bump&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;This package is designed to take a string, like &lt;code&gt;v1.0.0&lt;/code&gt; and allow you to &lt;code&gt;bump&lt;/code&gt; it with the command line.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;-in&lt;/code&gt; argument is the &lt;strong&gt;Input File&lt;/strong&gt; and it defaults to &lt;code&gt;./VERSION&lt;/code&gt; from the &lt;em&gt;current working directory&lt;/em&gt; of where &lt;code&gt;bump&lt;/code&gt; is being invoked.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;bump&lt;/code&gt; binary can intelligently bump &lt;code&gt;-in&lt;/code&gt; files like &lt;code&gt;go.mod&lt;/code&gt;, &lt;code&gt;package.json&lt;/code&gt;, &lt;code&gt;pom.xml&lt;/code&gt;, &lt;code&gt;Chart.yml&lt;/code&gt; and &lt;code&gt;Dockerfile&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;bump&lt;/code&gt; binary can have its default runtime manipulated using &lt;strong&gt;Environment Variables&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;bump&lt;/code&gt; binary leverages the &lt;code&gt;Exit Code 0&lt;/code&gt; or &lt;code&gt;Exit Code 1&lt;/code&gt; in order to use &lt;code&gt;bump&lt;/code&gt; in a DevOps pipeline.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;bump&lt;/code&gt; binary offers you &lt;code&gt;-json&lt;/code&gt; for &lt;em&gt;JSON&lt;/em&gt; Encoded output. &lt;/p&gt;

&lt;h3&gt;
  
  
  Version Format Priority
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;bump&lt;/code&gt; package can parse multiple version formats. To ensure accuracy, it checks for formats in a specific order, from most complex to least complex. This prevents a detailed pre-release version from being incorrectly identified as a simpler one.&lt;/p&gt;

&lt;p&gt;The priority is as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt; &lt;code&gt;v1.2.3-beta.4-alpha.5&lt;/code&gt; (Beta with Alpha)&lt;/li&gt;
&lt;li&gt; &lt;code&gt;v1.2.3-alpha.1&lt;/code&gt; (Alpha)&lt;/li&gt;
&lt;li&gt; &lt;code&gt;v1.2.3-beta.2&lt;/code&gt; (Beta)&lt;/li&gt;
&lt;li&gt; &lt;code&gt;v1.2.3-rc.1&lt;/code&gt; (Release Candidate)&lt;/li&gt;
&lt;li&gt; &lt;code&gt;v1.2.3-preview.7&lt;/code&gt; (Preview)&lt;/li&gt;
&lt;li&gt; &lt;code&gt;v1.2.3&lt;/code&gt; (Standard)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Using &lt;code&gt;bump&lt;/code&gt; in your future Go applications
&lt;/h3&gt;

&lt;p&gt;Whenever you use a &lt;code&gt;VERSION&lt;/code&gt; file you can easily begin using your file in your binary directly with: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//go:embed VERSION
var versionBytes []byte 

func Version() string {
    return strings.TrimSpace(string(versionBytes))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Testing Suite
&lt;/h2&gt;

&lt;p&gt;I want to speak about the testing suite. &lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;make test

# SAME AS

make test-cli
make test-unit
make test-bench
make test-fuzz      # 3s run
make test-fuzz-long # 30s run # not part of `make test`
make test-integration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;When each of these files are executed a corresponding &lt;code&gt;test-results/results.&amp;lt;test&amp;gt;.md&lt;/code&gt; file is created. &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Test&lt;/th&gt;
&lt;th&gt;Log File&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;test-unit&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;test-results/results.unit.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;test-bench&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;test-results/results.benchmark.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;test-fuzz&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;test-results/results.fuzz.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;test-fuzz-long&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;test-results/results.fuzz.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;test-integration&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;test-results/results.integration.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;test-cli&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;test-results/results.cli.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;When combined this is six &lt;strong&gt;6&lt;/strong&gt; &lt;em&gt;test strategies&lt;/em&gt; applied. Short fuzzy and long fuzzy are different types of tests and they measure different results. &lt;/p&gt;

&lt;p&gt;From the console you can run: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;./test.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;And this will run the &lt;a href="https://github.com/andreimerlescu/bump" rel="noopener noreferrer"&gt;bump&lt;/a&gt; in a test environment and verify that the binary behaves in the shell in an expected manner. &lt;/p&gt;

&lt;p&gt;The actual shell test suite is built on the concept of scenarios where they are executed in series inside the &lt;strong&gt;test.sh&lt;/strong&gt; script and built from the &lt;strong&gt;test_funcs.sh&lt;/strong&gt; and then verbosely described in &lt;strong&gt;test_scenarios.sh&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;As scenarios are added, they get added to this list of strings.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;declare&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;tests&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;
 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;scenario_01&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
 // other scenarios
 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;scenario_14&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for &lt;/span&gt;t &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;tests&lt;/span&gt;&lt;span class="p"&gt;[@]&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  &lt;/span&gt;run &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;t&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;done

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"All &lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;counter &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="nv"&gt;$counterName&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt; tests PASS!"&lt;/span&gt;

cleanup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then each scenario can be described like so:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Start with a populated VERSION file and bump its alpha&lt;/span&gt;
&lt;span class="nb"&gt;declare&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt; &lt;span class="nv"&gt;scenario_01&lt;/span&gt;&lt;span class="o"&gt;=(&lt;/span&gt;
  &lt;span class="s2"&gt;"echo &lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt;v1.0.0&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s2"&gt; &amp;gt; VERSION"&lt;/span&gt;
  &lt;span class="s2"&gt;"bump -check"&lt;/span&gt;
  &lt;span class="s2"&gt;"cat VERSION"&lt;/span&gt;
  &lt;span class="s2"&gt;"grep 'v1.0.0' VERSION"&lt;/span&gt;
  &lt;span class="s2"&gt;"bump -alpha"&lt;/span&gt;
  &lt;span class="s2"&gt;"grep 'v1.0.0' VERSION"&lt;/span&gt;
  &lt;span class="s2"&gt;"cat VERSION"&lt;/span&gt;
  &lt;span class="s2"&gt;"bump -alpha -write"&lt;/span&gt;
  &lt;span class="s2"&gt;"cat VERSION"&lt;/span&gt;
  &lt;span class="s2"&gt;"grep 'v1.0.0-alpha.1' VERSION"&lt;/span&gt;
  &lt;span class="s2"&gt;"rm VERSION"&lt;/span&gt;
&lt;span class="o"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When this runs, you are going to see the results in the &lt;code&gt;results.cli.md&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;andrei@bump.git:test.sh ⚡ Test #1 ⇒  echo "v1.0.0" &amp;gt; VERSION
andrei@bump.git:test.sh ⚡ Test #2 ⇒  bump -check
v1.0.0
andrei@bump.git:test.sh ⚡ Test #3 ⇒  cat VERSION
v1.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These tests are how you would use &lt;a href="https://github.com/andreimerlescu/bump" rel="noopener noreferrer"&gt;bump&lt;/a&gt; in the wild. &lt;/p&gt;

&lt;p&gt;In order for me to find these missing test cases, I asked AI to scan the entire code base using the &lt;a href="https://github.com/andreimerlescu/summarize" rel="noopener noreferrer"&gt;summarize&lt;/a&gt; summary of the "snapshot in time &lt;em&gt;between commits&lt;/em&gt;" and identify where the tests were missing coverage of native ways that people would use &lt;a href="https://github.com/andreimerlescu/bump" rel="noopener noreferrer"&gt;bump&lt;/a&gt; and it added a bunch of new tests that exposed the bugs and in &lt;code&gt;v1.1.0&lt;/code&gt; they were squashed. &lt;/p&gt;

&lt;h2&gt;
  
  
  The &lt;code&gt;bump&lt;/code&gt; package
&lt;/h2&gt;

&lt;p&gt;Did you know that you can take a &lt;code&gt;bump.Version{}&lt;/code&gt; and use it in your &lt;em&gt;own&lt;/em&gt; application?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// Example:&lt;/span&gt;
&lt;span class="c"&gt;//  version := bump.New()&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;parsed&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="k"&gt;interface&lt;/span&gt;&lt;span class="p"&gt;{}),&lt;/span&gt;
        &lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RWMutex&lt;/span&gt;&lt;span class="p"&gt;{},&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then from your application your version object can be interacted with in a manner that has extensive testing attached to it. The helpers in the application are built to support &lt;a href="https://github.com/andreimerlescu/igo" rel="noopener noreferrer"&gt;igo&lt;/a&gt; in your use of &lt;a href="https://github.com/andreimerlescu/bump" rel="noopener noreferrer"&gt;bump&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;What you can do with the &lt;code&gt;bump.Version{}&lt;/code&gt; struct: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;func BumpMajor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func BumpMinor&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func BumpPatch&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func BumpRC&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func BumpAlpha&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func BumpBeta&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func BumpPreview&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func Fix() error&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func Format(withPrefix string) string&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func LoadFile(path string) error&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func ParseFile(path string) error&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func Parse() error&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func Save(path string) error&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func Validate() error&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func String() string&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func Raw() string&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;func SetRaw(raw []byte)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;func NoPrefix() bool&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;&lt;code&gt;func Compare(*bump.Version) int&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And you can create new &lt;code&gt;bump&lt;/code&gt; structures using: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bump.New() *bump.Version&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bump.Parse(version string) (*bump.Version, error)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bump.Create(version, path string) (*bump.Version, error)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The example of the &lt;code&gt;.Create&lt;/code&gt; method is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;filepath&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Join&lt;/span&gt;&lt;span class="p"&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;TempDir&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="s"&gt;"VERSION"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;version&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bump&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"v1.2.3-beta.4"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
   &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But, its designed to be a go-to way of interacting with your &lt;code&gt;VERSION&lt;/code&gt; file in a go-to manner that is consistent across projects.  I like using it, and I add it to my &lt;a href="https://github.com/andreimerlescu/igo" rel="noopener noreferrer"&gt;igo&lt;/a&gt; custom extra packages on every install so its always accessible to me on every Go version that gets installed on my system. &lt;/p&gt;

</description>
      <category>cicd</category>
      <category>go</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>From Zero to Hero: Building a Waiting Room with `room`, `figtree`, and `verbose`</title>
      <dc:creator>Andrei Merlescu</dc:creator>
      <pubDate>Mon, 20 Apr 2026 18:21:13 +0000</pubDate>
      <link>https://dev.to/andreimerlescu/from-zero-to-hero-building-a-waiting-room-with-room-figtree-and-verbose-42fc</link>
      <guid>https://dev.to/andreimerlescu/from-zero-to-hero-building-a-waiting-room-with-room-figtree-and-verbose-42fc</guid>
      <description>&lt;p&gt;When your Go service gets hit by a traffic spike, you have three options: drop requests with a 429, queue them blindly with no ordering guarantee, or give every user a ticket, show them their position, and admit them in the order they arrived. The third option is what &lt;code&gt;room&lt;/code&gt; does. This tutorial wires it together with &lt;code&gt;figtree&lt;/code&gt; for runtime-adjustable configuration and &lt;code&gt;verbose&lt;/code&gt; for log output that is provably safe to hand to a support engineer — even after issuing paid VIP pass tokens.&lt;/p&gt;

&lt;p&gt;By the end you will have a running four-page Gin web application where the 6th concurrent request sees a live queue page, capacity adjusts from the environment without a restart, and every VIP pass token issued is scrubbed from the log file before it touches disk.&lt;/p&gt;

&lt;p&gt;All three packages are written by &lt;a href="https://github.com/andreimerlescu" rel="noopener noreferrer"&gt;Andrei Merlescu&lt;/a&gt;. The source for &lt;code&gt;room&lt;/code&gt; including a complete sample application lives at &lt;a href="https://github.com/andreimerlescu/room" rel="noopener noreferrer"&gt;github.com/andreimerlescu/room&lt;/a&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 1 — Scaffold and dependencies
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;room&lt;/code&gt; is the FIFO waiting room middleware for Gin, built on &lt;code&gt;sema&lt;/code&gt;. &lt;code&gt;figtree&lt;/code&gt; is the configuration resolver — file, then env, then CLI flags, with validators and live mutation tracking. &lt;code&gt;verbose&lt;/code&gt; is the logger that scrubs registered secrets from every line before it hits disk.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir basicwebapp &amp;amp;&amp;amp; cd basicwebapp
go mod init github.com/example/basicwebapp
go get github.com/andreimerlescu/room@latest
go get github.com/andreimerlescu/figtree/v2@latest
go get github.com/andreimerlescu/verbose@latest
go get github.com/gin-gonic/gin@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Create the files you will fill in across all remaining steps:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;touch main.go config.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Your &lt;code&gt;go.mod&lt;/code&gt; should look like this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module github.com/example/basicwebapp

go 1.22

require (
    github.com/andreimerlescu/figtree/v2  v2.0.14
    github.com/andreimerlescu/room        v1.0.0
    github.com/andreimerlescu/verbose     v0.2.0
    github.com/gin-gonic/gin             v1.9.1
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Your directory:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;basicwebapp/
├── go.mod
├── go.sum
├── config.yml
└── main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Nothing runs yet. That is fine.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2 — CLI design with figtree
&lt;/h2&gt;

&lt;p&gt;Before any server, any logger, any middleware — design the configuration. Every value the waiting room needs at runtime should be config-file-first, env-var-overridable, and named as a constant so typos cannot silently create a second key that is never read.&lt;/p&gt;

&lt;p&gt;Add this to &lt;code&gt;main.go&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// main.go — complete file at this stage

package main

import (
    "fmt"
    "os"
    "time"

    "github.com/andreimerlescu/figtree/v2"
)

// config keys — always constants, never raw strings at call sites
const (
    kPort            = "port"
    kLogDir          = "log-dir"
    kTruncate        = "truncate"
    kCap             = "room-cap"             // env: ROOM_CAP
    kReaperInterval  = "room-reaper-interval" // env: ROOM_REAPER_INTERVAL
    kRatePerPosition = "room-rate-per-pos"    // env: ROOM_RATE_PER_POS
    kPassDuration    = "room-pass-duration"   // env: ROOM_PASS_DURATION
    kSkipURL         = "room-skip-url"        // env: ROOM_SKIP_URL
)

func main() {
    // config-file-first — all values live in config.yml.
    // env vars override the file. CLI flags override env vars.
    figs := figtree.With(figtree.Options{
        Tracking:   true,
        Germinate:  true,
        Pollinate:  true,
        ConfigFile: "./config.yml",
    })

    // -- server --
    figs.NewInt(kPort, 8080, "port to listen on")
    figs.NewString(kLogDir, "./logs", "directory to write log files into")
    figs.NewBool(kTruncate, false, "truncate log file on each run")

    // -- room --
    figs.NewInt(kCap, 5, "max concurrent requests the room admits")
    // reaper-interval: integer multiplied by time.Second
    figs.NewUnitDuration(kReaperInterval, 10, time.Second,
        "how often the reaper evicts abandoned queue tickets")
    // rate-per-pos: float — $2.50 per position to skip the line
    figs.NewFloat64(kRatePerPosition, 2.50, "per-position cost to skip the queue ($)")
    // pass-duration: integer multiplied by time.Minute
    figs.NewUnitDuration(kPassDuration, 90, time.Minute,
        "how long a VIP pass stays valid after a skip-the-line payment")
    figs.NewString(kSkipURL, "/queue/purchase",
        "URL the waiting room page sends the user to for skip-the-line payment")

    // Problems() catches developer mistakes — duplicate keys, bad
    // validator combos — before Load() runs. These are your bugs.
    if problems := figs.Problems(); len(problems) &amp;gt; 0 {
        for _, p := range problems {
            fmt.Fprintf(os.Stderr, "figtree problem: %v\n", p)
        }
        os.Exit(1)
    }

    // Load resolves: config.yml → env vars → CLI flags
    if err := figs.Load(); err != nil {
        fmt.Fprintf(os.Stderr, "figtree.Load: %v\n", err)
        os.Exit(1)
    }

    // Print everything so you can verify what loaded — fmt.Println for now.
    // verbose replaces this in Step 4.
    fmt.Printf("port=%d log-dir=%s cap=%d reaper=%s rate=%.2f pass=%s skip-url=%s\n",
        *figs.Int(kPort),
        *figs.String(kLogDir),
        *figs.Int(kCap),
        *figs.Duration(kReaperInterval),
        *figs.Float64(kRatePerPosition),
        *figs.Duration(kPassDuration),
        *figs.String(kSkipURL),
    )
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Create &lt;code&gt;config.yml&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;port:                 8080
log-dir:              "./logs"
truncate:             false
room-cap:             5
room-reaper-interval: 10
room-rate-per-pos:    2.50
room-pass-duration:   90
room-skip-url:        "/queue/purchase"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Run it:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;go run .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You should see all values printed. Try overriding cap from the environment:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ROOM_CAP=10 go run .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The cap changes without touching &lt;code&gt;config.yml&lt;/code&gt;. That is the priority chain working.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mistake trap:&lt;/strong&gt; Set &lt;code&gt;room-cap: 0&lt;/code&gt; in &lt;code&gt;config.yml&lt;/code&gt; and run. figtree loads it without complaint at this stage — validators are not wired yet. You will fix this in Step 3 and &lt;code&gt;0&lt;/code&gt; will be rejected before the server starts.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 3 — Validators, Problems(), and mutations
&lt;/h2&gt;

&lt;p&gt;Now that the tree is declared, lock it down. Validators reject bad values before the server starts. The mutations goroutine is where the live capacity adjustment lives — when &lt;code&gt;ROOM_CAP&lt;/code&gt; changes in the environment, &lt;code&gt;wr.SetCap&lt;/code&gt; is called immediately, no restart required.&lt;/p&gt;

&lt;p&gt;Add validators after each &lt;code&gt;New*()&lt;/code&gt; call:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;figs.NewInt(kPort, 8080, "port to listen on")
figs.WithValidator(kPort, figtree.AssureIntInRange(1024, 65535))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;figs.NewString(kLogDir, "./logs", "directory to write log files into")
figs.WithValidator(kLogDir, figtree.AssureStringNotEmpty)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;figs.NewInt(kCap, 5, "max concurrent requests the room admits")
// cap must be at least 1; room.Init rejects 0
figs.WithValidator(kCap, figtree.AssureIntGreaterThan(0))
figs.WithValidator(kCap, figtree.AssureIntLessThan(10001))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;figs.NewUnitDuration(kReaperInterval, 10, time.Second,
    "how often the reaper evicts abandoned queue tickets")
// room accepts 5s–24h; match its constraints here
figs.WithValidator(kReaperInterval, figtree.AssureDurationMin(5*time.Second))
figs.WithValidator(kReaperInterval, figtree.AssureDurationMax(24*time.Hour))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;figs.NewFloat64(kRatePerPosition, 2.50, "per-position cost to skip the queue ($)")
// rate must be positive
figs.WithValidator(kRatePerPosition, figtree.AssureFloat64Positive)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;figs.NewUnitDuration(kPassDuration, 90, time.Minute,
    "how long a VIP pass stays valid after a skip-the-line payment")
// room accepts 1m–24h; match its constraints
figs.WithValidator(kPassDuration, figtree.AssureDurationMin(1*time.Minute))
figs.WithValidator(kPassDuration, figtree.AssureDurationMax(24*time.Hour))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;




&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;figs.NewString(kSkipURL, "/queue/purchase",
    "URL the waiting room page sends the user to for skip-the-line payment")
figs.WithValidator(kSkipURL, figtree.AssureStringNotEmpty)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now add the mutations goroutine &lt;strong&gt;after&lt;/strong&gt; &lt;code&gt;figs.Load()&lt;/code&gt;. The &lt;code&gt;wr&lt;/code&gt; variable is declared as a package-level var — we need it accessible from the goroutine and from main:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if err := figs.Load(); err != nil {
    fmt.Fprintf(os.Stderr, "figtree.Load: %v\n", err)
    os.Exit(1)
}

// mutations goroutine starts AFTER Load()
// because the channel is only meaningful once the tree is live
go func() {
    for m := range figs.Mutations() {
        // verbose.Printf replaces fmt.Println in Step 4
        fmt.Printf("config mutation: %s changed from %v to %v at %s\n",
            m.Property, m.Old, m.New, m.When)

        // live capacity adjustment — when ROOM_CAP changes in the
        // environment, the room adjusts immediately.
        // NOTE: shrinking capacity drains in-flight work first via
        // sema.SetCap — expect a brief pause in admissions when
        // reducing cap under load. This is intentional and safe.
        if m.Property == kCap {
            if newCap, ok := m.New.(int); ok &amp;amp;&amp;amp; wr != nil {
                if err := wr.SetCap(int32(newCap)); err != nil {
                    fmt.Printf("wr.SetCap: %v\n", err)
                }
            }
        }
    }
}()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Add &lt;code&gt;wr&lt;/code&gt; as a package-level variable above &lt;code&gt;main&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;var wr *room.WaitingRoom
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Add &lt;code&gt;room&lt;/code&gt; to imports:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"github.com/andreimerlescu/room"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Run it and try the live cap adjustment — open a second terminal while the server is running (after Step 5 wires up the HTTP server) and run:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export ROOM_CAP=10
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Within one poll cycle you will see the mutation log entry appear and the room silently expand.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mistake trap:&lt;/strong&gt; Move the mutations goroutine to before &lt;code&gt;figs.Load()&lt;/code&gt; and run it. The channel exists but the tree has not resolved its values yet — mutations fired during load will not be seen. Always start the mutations goroutine after &lt;code&gt;Load()&lt;/code&gt; returns.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 4 — Wire verbose, replace fmt
&lt;/h2&gt;

&lt;p&gt;Now that figtree is loading and validating correctly, wire the logger. verbose must be initialised before any log call — the moment it is up, all subsequent log lines through &lt;code&gt;verbose.Printf&lt;/code&gt; are scrubbed against the registered secrets registry.&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;verbose&lt;/code&gt; to imports:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;"github.com/andreimerlescu/verbose"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Add verbose initialisation right after the mutations goroutine:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if err := verbose.NewLogger(verbose.Options{
    Dir:      *figs.String(kLogDir),
    Name:     "basicwebapp",
    Truncate: *figs.Bool(kTruncate),
    DirMode:  0o755,
    FileMode: 0o640,
}); err != nil {
    fmt.Fprintf(os.Stderr, "verbose.NewLogger: %v\n", err)
    os.Exit(1)
}
verbose.Printf("basicwebapp starting (verbose v%s / figtree v%s)",
    verbose.VERSION, figtree.Version())
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Replace the &lt;code&gt;fmt.Printf&lt;/code&gt; startup summary:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// remove this
fmt.Printf("port=%d log-dir=%s cap=%d reaper=%s rate=%.2f pass=%s skip-url=%s\n",
    *figs.Int(kPort),
    *figs.String(kLogDir),
    *figs.Int(kCap),
    *figs.Duration(kReaperInterval),
    *figs.Float64(kRatePerPosition),
    *figs.Duration(kPassDuration),
    *figs.String(kSkipURL),
)

// replace with this
verbose.Printf("config: port=%d log-dir=%s cap=%d reaper=%s rate=%.2f pass=%s skip-url=%s",
    *figs.Int(kPort),
    *figs.String(kLogDir),
    *figs.Int(kCap),
    *figs.Duration(kReaperInterval),
    *figs.Float64(kRatePerPosition),
    *figs.Duration(kPassDuration),
    *figs.String(kSkipURL),
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Replace the &lt;code&gt;fmt.Printf&lt;/code&gt; in the mutations goroutine:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// remove this
fmt.Printf("config mutation: %s changed from %v to %v at %s\n",
    m.Property, m.Old, m.New, m.When)

// replace with this
verbose.Printf("config mutation: %s changed from %v to %v at %s",
    m.Property, m.Old, m.New, m.When)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Add the &lt;code&gt;roomLog&lt;/code&gt; helper below &lt;code&gt;main&lt;/code&gt; — all room lifecycle events flow through here:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// roomLog writes a room event line to verbose so it appears in the log
// file with a consistent tag. Filter by tag in the shell:
//
//   tail -f logs/basicwebapp.log | grep '\[FULL\]'
//   tail -f logs/basicwebapp.log | grep '\[QUEUE\]'
func roomLog(tag, msg string) {
    verbose.Printf("[ %s ] %s", tag, msg)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mistake trap:&lt;/strong&gt; Call &lt;code&gt;verbose.Printf("test")&lt;/code&gt; before &lt;code&gt;verbose.NewLogger&lt;/code&gt; — move it above the &lt;code&gt;NewLogger&lt;/code&gt; block and run. You will see the message printed to stderr with "NewLogger or SetLogger has not been called". verbose does not panic — it fails open to stderr. Move the call back below &lt;code&gt;NewLogger&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 5 — Initialise the WaitingRoom
&lt;/h2&gt;

&lt;p&gt;With logging solid, initialise the room. Two things matter here: use &lt;code&gt;gin.New()&lt;/code&gt; not &lt;code&gt;gin.Default()&lt;/code&gt;, and initialise the room before registering any routes.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Why &lt;code&gt;gin.New()&lt;/code&gt; not &lt;code&gt;gin.Default()&lt;/code&gt;:&lt;/strong&gt; &lt;code&gt;gin.Default()&lt;/code&gt; installs gin's own &lt;code&gt;Logger&lt;/code&gt; middleware, which buffers each log line and prints it after the handler returns. During load testing that means you see nothing until the request is already complete — room events and request logs appear out of order and the queue activity is invisible in real time. &lt;code&gt;gin.New()&lt;/code&gt; gives you a blank engine. You install &lt;code&gt;gin.Recovery()&lt;/code&gt; manually and add your own logger that prints on entry and exit.&lt;/p&gt;

&lt;p&gt;Add the Gin and room setup after the verbose startup log:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;r := gin.New()
r.Use(gin.Recovery())
r.Use(requestLogger()) // prints on entry AND exit — see helper below

// initialise the WaitingRoom with the cap from figtree
wr = &amp;amp;room.WaitingRoom{}
if err := wr.Init(int32(*figs.Int(kCap))); err != nil {
    verbose.TracefReturn("room.Init: %v", err)
    os.Exit(1)
}
defer wr.Stop()

// apply all runtime-adjustable settings from figtree
if err := wr.SetReaperInterval(*figs.Duration(kReaperInterval)); err != nil {
    verbose.TracefReturn("wr.SetReaperInterval: %v", err)
    os.Exit(1)
}
if err := wr.SetPassDuration(*figs.Duration(kPassDuration)); err != nil {
    verbose.TracefReturn("wr.SetPassDuration: %v", err)
    os.Exit(1)
}

// rate function reads kRatePerPosition from figtree on every call
// so it stays current if the value changes via Pollinate
wr.SetRateFunc(func(depth int64) float64 {
    return *figs.Float64(kRatePerPosition)
})

wr.SetSkipURL(*figs.String(kSkipURL))
// SetSecureCookie defaults to false for local dev — set true in production
// behind TLS or a TLS-terminating proxy
wr.SetSecureCookie(false)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Add the &lt;code&gt;requestLogger&lt;/code&gt; helper below &lt;code&gt;roomLog&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// requestLogger returns a Gin middleware that prints on request arrival
// and again on completion, so you can see room events interleaved with
// request lifecycle in real time during load tests.
func requestLogger() gin.HandlerFunc {
    return func(c *gin.Context) {
        // skip the status polling endpoint — it fires every 3s per
        // queued client and would bury room events in noise
        if c.Request.URL.Path == "/queue/status" {
            c.Next()
            return
        }
        start := time.Now()
        verbose.Printf("[ REQ ] --&amp;gt; %s %s  remote=%s",
            c.Request.Method, c.Request.URL.Path, c.ClientIP())
        c.Next()
        verbose.Printf("[ REQ ] &amp;lt;-- %s %s  status=%d  latency=%s",
            c.Request.Method, c.Request.URL.Path,
            c.Writer.Status(), time.Since(start).Round(time.Millisecond))
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mistake trap:&lt;/strong&gt; Use &lt;code&gt;gin.Default()&lt;/code&gt; instead of &lt;code&gt;gin.New()&lt;/code&gt;. Run the server and generate load with &lt;code&gt;ab -c 30 -n 200 http://localhost:8080/about&lt;/code&gt;. Watch the logs — room events (&lt;code&gt;[FULL]&lt;/code&gt;, &lt;code&gt;[QUEUE]&lt;/code&gt;, &lt;code&gt;[ENTER]&lt;/code&gt;) arrive in batches after each handler completes rather than in real time. You cannot see the queue building. Switch to &lt;code&gt;gin.New()&lt;/code&gt; and the events appear as they happen.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 6 — Lifecycle callbacks
&lt;/h2&gt;

&lt;p&gt;Callbacks are what you see in the log during a load test. Register all of them before &lt;code&gt;wr.RegisterRoutes(r)&lt;/code&gt; so no events are missed during the startup surge.&lt;/p&gt;

&lt;p&gt;Add this block after the &lt;code&gt;wr.SetSecureCookie&lt;/code&gt; call:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// register ALL callbacks before RegisterRoutes
wr.On(room.EventFull, func(s room.Snapshot) {
    roomLog("FULL   ", fmt.Sprintf(
        "capacity reached  occupancy=%d/%d  queue=%d  util=%.0f%%",
        s.Occupancy, s.Capacity, s.QueueDepth, pct(s.Occupancy, s.Capacity),
    ))
})
wr.On(room.EventDrain, func(s room.Snapshot) {
    roomLog("DRAIN  ", fmt.Sprintf(
        "room no longer full  occupancy=%d/%d  queue=%d",
        s.Occupancy, s.Capacity, s.QueueDepth,
    ))
})
wr.On(room.EventQueue, func(s room.Snapshot) {
    roomLog("QUEUE  ", fmt.Sprintf(
        "request queued  depth=%d  occupancy=%d/%d  util=%.0f%%",
        s.QueueDepth, s.Occupancy, s.Capacity, pct(s.Occupancy, s.Capacity),
    ))
})
wr.On(room.EventEnter, func(s room.Snapshot) {
    roomLog("ENTER  ", fmt.Sprintf(
        "slot acquired  occupancy=%d/%d  queue=%d  util=%.0f%%",
        s.Occupancy, s.Capacity, s.QueueDepth, pct(s.Occupancy, s.Capacity),
    ))
})
wr.On(room.EventExit, func(s room.Snapshot) {
    roomLog("EXIT   ", fmt.Sprintf(
        "slot released  occupancy=%d/%d  queue=%d  util=%.0f%%",
        s.Occupancy, s.Capacity, s.QueueDepth, pct(s.Occupancy, s.Capacity),
    ))
})
wr.On(room.EventEvict, func(s room.Snapshot) {
    roomLog("EVICT  ", fmt.Sprintf(
        "ghost ticket removed  queue=%d  occupancy=%d/%d",
        s.QueueDepth, s.Occupancy, s.Capacity,
    ))
})
wr.On(room.EventTimeout, func(s room.Snapshot) {
    roomLog("TIMEOUT", fmt.Sprintf(
        "context cancelled before admission  occupancy=%d/%d  queue=%d",
        s.Occupancy, s.Capacity, s.QueueDepth,
    ))
})
wr.On(room.EventPromote, func(s room.Snapshot) {
    roomLog("PROMOTE", fmt.Sprintf(
        "client promoted to front  occupancy=%d/%d  queue=%d",
        s.Occupancy, s.Capacity, s.QueueDepth,
    ))
})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Add the &lt;code&gt;pct&lt;/code&gt; helper below &lt;code&gt;requestLogger&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// pct converts occupancy/capacity to a display percentage.
func pct(occupancy, capacity int) float64 {
    if capacity == 0 {
        return 0
    }
    return float64(occupancy) / float64(capacity) * 100
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mistake trap:&lt;/strong&gt; Register an &lt;code&gt;EventFull&lt;/code&gt; callback after &lt;code&gt;wr.RegisterRoutes(r)&lt;/code&gt; and immediately fire a load test. If the room fills during &lt;code&gt;RegisterRoutes&lt;/code&gt;, you miss the first transition. Always register callbacks before &lt;code&gt;RegisterRoutes&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 7 — Routes, the waiting room, and runtime secret registration
&lt;/h2&gt;

&lt;p&gt;This is the step where verbose earns its place. The moment &lt;code&gt;PromoteTokenToFront&lt;/code&gt; returns a &lt;code&gt;PassToken&lt;/code&gt;, that token is registered as a verbose secret &lt;strong&gt;before&lt;/strong&gt; anything else touches it — before it is set as a cookie, before it is mentioned in any log line. The sequence is non-negotiable: register first, then do everything else.&lt;/p&gt;

&lt;p&gt;The rule for what goes in logs: log metadata about the event (cost, whether a pass was issued, queue depth), never the token value or any substring of it. A truncated token prefix is not protected by verbose scrubbing because verbose matches the full registered value — logging &lt;code&gt;token=%.8s...&lt;/code&gt; bypasses the scrubber entirely. If a value is sensitive enough to register with verbose, it is sensitive enough to keep entirely out of log lines.&lt;/p&gt;

&lt;p&gt;Add routes and the server to &lt;code&gt;main&lt;/code&gt;, after the callbacks block:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// payment routes — registered BEFORE RegisterRoutes so they bypass the queue
r.GET("/queue/purchase", handlePurchasePage)
r.POST("/queue/purchase/confirm", handlePurchaseConfirm)

// RegisterRoutes installs:
//   OPTIONS /queue/status  (CORS preflight)
//   GET     /queue/status  (polling endpoint for the waiting room page)
//   r.Use(wr.Middleware()) (gates every route registered after this line)
wr.RegisterRoutes(r)

// application routes — all gated by the waiting room
r.GET("/",        homePage)
r.GET("/about",   aboutPage)
r.GET("/pricing", pricingPage)
r.GET("/contact", contactPage)

addr := fmt.Sprintf(":%d", *figs.Int(kPort))
verbose.Printf("basicwebapp listening on http://localhost%s  cap=%d  rate=$%.2f/pos  pass=%s",
    addr, wr.Cap(), *figs.Float64(kRatePerPosition), wr.PassDuration())

if err := r.Run(addr); err != nil {
    verbose.TracefReturn("r.Run: %v", err)
    os.Exit(1)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Add the payment handlers and page handlers below &lt;code&gt;pct&lt;/code&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// handlePurchasePage shows the payment confirmation page.
// In production: redirect to a Stripe Checkout session.
func handlePurchasePage(c *gin.Context) {
    cookie, err := c.Request.Cookie("room_ticket")
    if err != nil || cookie.Value == "" {
        c.Data(http.StatusBadRequest, "text/html; charset=utf-8", page(
            "Error", "&amp;lt;h1&amp;gt;No queue ticket found&amp;lt;/h1&amp;gt;&amp;lt;a href='/'&amp;gt;← Back&amp;lt;/a&amp;gt;",
        ))
        return
    }

    // check for an active VIP pass — no need to pay again
    if passCookie, err := c.Request.Cookie("room_pass"); err == nil {
        if wr.HasValidPass(passCookie.Value) {
            c.Data(http.StatusOK, "text/html; charset=utf-8", page(
                "VIP pass active",
                "&amp;lt;h1&amp;gt;Your VIP pass is still active&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;You'll be auto-promoted — no payment needed.&amp;lt;/p&amp;gt;&amp;lt;a href='/'&amp;gt;← Back&amp;lt;/a&amp;gt;",
            ))
            return
        }
    }

    cost, err := wr.QuoteCost(cookie.Value, 1)
    if err != nil {
        c.Data(http.StatusOK, "text/html; charset=utf-8", page(
            "Skip the line",
            fmt.Sprintf("&amp;lt;h1&amp;gt;Skip the line&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;%s&amp;lt;/p&amp;gt;&amp;lt;a href='/'&amp;gt;← Back&amp;lt;/a&amp;gt;", err.Error()),
        ))
        return
    }

    verbose.Printf("GET /queue/purchase — cost=$%.2f queue=%d", cost, wr.QueueDepth())
    c.Data(http.StatusOK, "text/html; charset=utf-8", purchasePage(cost, wr.PassDuration()))
}

// handlePurchaseConfirm processes the payment and promotes the token.
// In production: verify Stripe webhook signature before calling PromoteTokenToFront.
func handlePurchaseConfirm(c *gin.Context) {
    cookie, err := c.Request.Cookie("room_ticket")
    if err != nil || cookie.Value == "" {
        c.JSON(http.StatusBadRequest, gin.H{"error": "no room_ticket cookie"})
        return
    }

    result, err := wr.PromoteTokenToFront(cookie.Value)
    if err != nil {
        verbose.Printf("POST /queue/purchase/confirm — promotion failed: %v", err)
        c.Data(http.StatusOK, "text/html; charset=utf-8", page(
            "Payment failed",
            fmt.Sprintf("&amp;lt;h1&amp;gt;Something went wrong&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;%s&amp;lt;/p&amp;gt;&amp;lt;a href='/'&amp;gt;← Back&amp;lt;/a&amp;gt;", err.Error()),
        ))
        return
    }

    // ── THE KEY MOMENT ────────────────────────────────────────────────────
    // Register the VIP pass token with verbose FIRST — before the cookie
    // is set, before anything is logged, before the response is written.
    //
    // After this line, if the pass token value appears anywhere in any
    // log line this process writes, verbose replaces it with [VIP_PASS].
    //
    // Do NOT log the token value, any prefix of it, or any derivative.
    // verbose scrubs the full registered value — a truncated prefix escapes
    // the scrubber entirely. Log metadata only: cost, whether a pass was
    // issued, queue depth.
    if result.PassToken != "" {
        if err := verbose.AddSecret(verbose.SecretBytes(result.PassToken), "[VIP_PASS]"); err != nil {
            verbose.Printf("POST /queue/purchase/confirm — failed to protect pass token: %v", err)
        }
    }

    // safe to log now — pass token is scrubbed, metadata only
    verbose.Printf("POST /queue/purchase/confirm — promoted  cost=$%.2f  pass_issued=%v  queue=%d",
        result.Cost, result.PassToken != "", wr.QueueDepth())

    // set the VIP pass cookie so the client is auto-promoted on re-entry
    if result.PassToken != "" {
        http.SetCookie(c.Writer, &amp;amp;http.Cookie{
            Name:     "room_pass",
            Value:    result.PassToken,
            Path:     wr.CookiePath(),
            MaxAge:   int(wr.PassDuration().Seconds()),
            HttpOnly: true,
            Secure:   false, // set true in production behind TLS
            SameSite: http.SameSiteLaxMode,
        })
    }

    passMsg := ""
    if result.PassToken != "" {
        passMsg = fmt.Sprintf("&amp;lt;p&amp;gt;Your VIP pass is valid for &amp;lt;strong&amp;gt;%s&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;",
            wr.PassDuration().Round(time.Minute))
    }
    c.Data(http.StatusOK, "text/html; charset=utf-8", page("Payment confirmed",
        fmt.Sprintf(`&amp;lt;h1&amp;gt;Payment confirmed — $%.2f&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;You've been moved to the front of the line!&amp;lt;/p&amp;gt;%s
        &amp;lt;script&amp;gt;setTimeout(function(){ window.location.href = "/"; }, 2000);&amp;lt;/script&amp;gt;
        &amp;lt;noscript&amp;gt;&amp;lt;a href="/"&amp;gt;← Click here&amp;lt;/a&amp;gt;&amp;lt;/noscript&amp;gt;`, result.Cost, passMsg),
    ))
}

// ── page handlers — all gated by the waiting room ─────────────────────────

const simulatedLatency = 500 * time.Millisecond

func homePage(c *gin.Context) {
    time.Sleep(simulatedLatency)
    c.Data(http.StatusOK, "text/html; charset=utf-8", page("Home",
        `&amp;lt;h1&amp;gt;Welcome&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;This server admits at most &amp;lt;strong&amp;gt;5 concurrent requests&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
        &amp;lt;p&amp;gt;Run &amp;lt;code&amp;gt;ab -c 30 -n 500 http://localhost:8080/about&amp;lt;/code&amp;gt; and watch the logs.&amp;lt;/p&amp;gt;
        &amp;lt;nav&amp;gt;&amp;lt;a href="/about"&amp;gt;About&amp;lt;/a&amp;gt; · &amp;lt;a href="/pricing"&amp;gt;Pricing&amp;lt;/a&amp;gt; · &amp;lt;a href="/contact"&amp;gt;Contact&amp;lt;/a&amp;gt;&amp;lt;/nav&amp;gt;`,
    ))
}

func aboutPage(c *gin.Context) {
    time.Sleep(simulatedLatency)
    c.Data(http.StatusOK, "text/html; charset=utf-8", page("About",
        `&amp;lt;h1&amp;gt;About&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;Built with &amp;lt;strong&amp;gt;room&amp;lt;/strong&amp;gt;, &amp;lt;strong&amp;gt;figtree&amp;lt;/strong&amp;gt;, and &amp;lt;strong&amp;gt;verbose&amp;lt;/strong&amp;gt;.&amp;lt;/p&amp;gt;
        &amp;lt;a href="/"&amp;gt;← Home&amp;lt;/a&amp;gt;`,
    ))
}

func pricingPage(c *gin.Context) {
    time.Sleep(simulatedLatency)
    c.Data(http.StatusOK, "text/html; charset=utf-8", page("Pricing",
        `&amp;lt;h1&amp;gt;Pricing&amp;lt;/h1&amp;gt;
        &amp;lt;p&amp;gt;Skip the line: &amp;lt;strong&amp;gt;$2.50/position&amp;lt;/strong&amp;gt; + 90-minute VIP pass.&amp;lt;/p&amp;gt;
        &amp;lt;a href="/"&amp;gt;← Home&amp;lt;/a&amp;gt;`,
    ))
}

func contactPage(c *gin.Context) {
    time.Sleep(simulatedLatency)
    c.Data(http.StatusOK, "text/html; charset=utf-8", page("Contact",
        `&amp;lt;h1&amp;gt;Contact&amp;lt;/h1&amp;gt;&amp;lt;p&amp;gt;hello@example.com&amp;lt;/p&amp;gt;&amp;lt;a href="/"&amp;gt;← Home&amp;lt;/a&amp;gt;`,
    ))
}

// page wraps a body fragment in a complete HTML document.
func page(title, body string) []byte {
    return []byte(`&amp;lt;!DOCTYPE html&amp;gt;&amp;lt;html lang="en"&amp;gt;&amp;lt;head&amp;gt;
    &amp;lt;meta charset="UTF-8"&amp;gt;&amp;lt;title&amp;gt;` + title + `&amp;lt;/title&amp;gt;
    &amp;lt;style&amp;gt;body{font-family:system-ui,sans-serif;max-width:700px;margin:4rem auto;padding:0 1.5rem}
    h1{margin-bottom:1rem}p{margin-bottom:1rem}code{background:#f0f0f0;padding:.1em .4em;border-radius:3px}
    a{color:#6c8ef5}&amp;lt;/style&amp;gt;&amp;lt;/head&amp;gt;&amp;lt;body&amp;gt;` + body + `&amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;`)
}

// purchasePage renders the skip-the-line confirmation page.
func purchasePage(cost float64, passDur time.Duration) []byte {
    passNote := ""
    if passDur &amp;gt; 0 {
        passNote = fmt.Sprintf(
            `&amp;lt;p&amp;gt;Includes a &amp;lt;strong&amp;gt;%s VIP pass&amp;lt;/strong&amp;gt; — re-enter the queue anytime during that window and skip for free.&amp;lt;/p&amp;gt;`,
            passDur.Round(time.Minute))
    }
    return []byte(fmt.Sprintf(`&amp;lt;!DOCTYPE html&amp;gt;&amp;lt;html&amp;gt;&amp;lt;head&amp;gt;&amp;lt;meta charset="UTF-8"&amp;gt;
    &amp;lt;title&amp;gt;Skip the line&amp;lt;/title&amp;gt;&amp;lt;/head&amp;gt;&amp;lt;body style="font-family:system-ui;max-width:500px;margin:4rem auto;padding:0 1.5rem"&amp;gt;
    &amp;lt;h1&amp;gt;Skip the line — $%.2f&amp;lt;/h1&amp;gt;%s
    &amp;lt;form method="POST" action="/queue/purchase/confirm"&amp;gt;
      &amp;lt;button type="submit" style="background:#6c8ef5;color:#fff;border:none;padding:.75rem 2rem;border-radius:8px;font-size:1rem;cursor:pointer"&amp;gt;
        Confirm payment — $%.2f
      &amp;lt;/button&amp;gt;
    &amp;lt;/form&amp;gt;
    &amp;lt;p style="margin-top:1rem;font-size:.8rem;color:#666"&amp;gt;Demo mode — no real payment processed.&amp;lt;/p&amp;gt;
    &amp;lt;a href="/"&amp;gt;← Back to waiting room&amp;lt;/a&amp;gt;
    &amp;lt;/body&amp;gt;&amp;lt;/html&amp;gt;`, cost, passNote, cost))
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Add &lt;code&gt;net/http&lt;/code&gt; to imports.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mistake trap:&lt;/strong&gt; Move &lt;code&gt;verbose.AddSecret&lt;/code&gt; to after &lt;code&gt;http.SetCookie&lt;/code&gt;. Run the server, issue a key by clicking "Pay to skip", then check &lt;code&gt;logs/basicwebapp.log&lt;/code&gt;. Find the &lt;code&gt;POST /queue/purchase/confirm&lt;/code&gt; line. The pass token appears in plaintext because verbose did not know about it yet. Move &lt;code&gt;AddSecret&lt;/code&gt; back to immediately after &lt;code&gt;PromoteTokenToFront&lt;/code&gt; returns, before anything else.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 8 — The bash test script
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;test.sh&lt;/code&gt;. This script is the pass/fail gate. Exit &lt;code&gt;0&lt;/code&gt; means every assertion passed — your implementation is correct. It fails fast on the first failed assertion and tells you which step to revisit.&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#!/usr/bin/env bash
set -euo pipefail

# ── dependency check ──────────────────────────────────────────────────────────
if ! command -v curl &amp;amp;&amp;gt;/dev/null; then
    echo "ERROR: curl is required."; exit 1
fi
if ! command -v jq &amp;amp;&amp;gt;/dev/null; then
    echo "ERROR: jq is required."
    echo "  macOS: brew install jq"
    echo "  Linux: sudo apt install jq  OR  sudo yum install jq"
    exit 1
fi
if ! command -v ab &amp;amp;&amp;gt;/dev/null; then
    echo "ERROR: apache bench (ab) is required."
    echo "  macOS: brew install httpd"
    echo "  Linux: sudo apt install apache2-utils"
    exit 1
fi

# ── config ────────────────────────────────────────────────────────────────────
BASE="http://127.0.0.1:8080"
LOG_FILE="./logs/basicwebapp.log"

# ── helpers ───────────────────────────────────────────────────────────────────
pass() { echo "  PASS: $1"; }
fail() { echo "  FAIL: $1 — $2"; exit 1; }

assert_log_contains() {
    local label="$1" pattern="$2" step="$3"
    if ! grep -qF "$pattern" "$LOG_FILE" 2&amp;gt;/dev/null; then
        fail "$label" "expected '$pattern' in log — not found (see step $step)"
    fi
    pass "$label"
}

assert_no_plaintext() {
    local label="$1" secret="$2" step="$3"
    if grep -qF "$secret" "$LOG_FILE" 2&amp;gt;/dev/null; then
        fail "$label" "plaintext secret found in log (see step $step)"
    fi
    pass "$label"
}

assert_status() {
    local label="$1" expected="$2" actual="$3" step="$4"
    if [ "$actual" -ne "$expected" ]; then
        fail "$label" "expected HTTP $expected, got $actual (see step $step)"
    fi
    pass "$label"
}

# ── build and start ───────────────────────────────────────────────────────────
echo "=&amp;gt; building..."
go build -o basicwebapp_bin . || { echo "Build failed."; exit 1; }

echo "=&amp;gt; starting server..."
./basicwebapp_bin &amp;amp;
SERVER_PID=$!
trap 'kill $SERVER_PID 2&amp;gt;/dev/null; rm -f basicwebapp_bin' EXIT

for i in $(seq 1 20); do
    if curl -sf "$BASE/" &amp;amp;&amp;gt;/dev/null; then break; fi
    sleep 0.5
done

echo ""
echo "── assertion 1: server started and log file exists (Step 4) ─────────────"
if [ ! -f "$LOG_FILE" ]; then
    fail "log file exists" "logs/basicwebapp.log not found — check verbose.NewLogger (see step 4)"
fi
pass "log file exists"

echo ""
echo "── assertion 2: config loaded and logged (Step 2 + 4) ───────────────────"
assert_log_contains "config line in log" "config:" "2"

echo ""
echo "── assertion 3: waiting room activates under load (Step 5 + 6) ──────────"
echo "   running ab -c 30 -n 100 $BASE/about (10s)..."
ab -c 30 -n 100 -t 10 "$BASE/about" &amp;amp;&amp;gt;/dev/null || true
sleep 2
assert_log_contains "[FULL] in log"  "[ FULL   ]"  "6"
assert_log_contains "[QUEUE] in log" "[ QUEUE  ]"  "6"
assert_log_contains "[ENTER] in log" "[ ENTER  ]"  "6"

echo ""
echo "── assertion 4: purchase page accessible (Step 7) ───────────────────────"
STATUS=$(curl -s -o /dev/null -w "%{http_code}" "$BASE/queue/purchase")
assert_status "GET /queue/purchase returns 400" 400 "$STATUS" "7"

echo ""
echo "── assertion 5: VIP pass token scrubbed from log (Step 7) ──────────────"
COOKIE_JAR=$(mktemp)
for i in $(seq 1 6); do
    curl -s -o /dev/null "$BASE/about" &amp;amp;
done
sleep 0.3
curl -s -o /dev/null -c "$COOKIE_JAR" "$BASE/about" || true
TICKET=$(grep "room_ticket" "$COOKIE_JAR" | awk '{print $NF}' || true)

if [ -n "$TICKET" ]; then
    PASS_TOKEN=$(curl -s -b "$COOKIE_JAR" -c "$COOKIE_JAR" \
        -X POST "$BASE/queue/purchase/confirm" \
        -D - -o /dev/null 2&amp;gt;/dev/null \
        | grep -i "room_pass" | grep -oP 'room_pass=\K[^;]+' || true)

    if [ -n "$PASS_TOKEN" ]; then
        assert_no_plaintext "VIP pass token not in log" "$PASS_TOKEN" "7"
        assert_log_contains "[VIP_PASS] marker in log" "[VIP_PASS]" "7"
    else
        pass "VIP pass token not in log (no pass token issued — room may not have been full)"
    fi
else
    pass "VIP pass token check skipped (could not obtain room_ticket cookie)"
fi
rm -f "$COOKIE_JAR"

wait 2&amp;gt;/dev/null || true

echo ""
echo "── assertion 6: ROOM_CAP mutation logged (Step 3) ───────────────────────"
export ROOM_CAP=8
echo "   waiting up to 90s for ROOM_CAP mutation to appear in log..."
for i in $(seq 1 18); do
    if grep -qF "room-cap" "$LOG_FILE" 2&amp;gt;/dev/null; then
        pass "ROOM_CAP mutation in log"
        break
    fi
    sleep 5
    if [ "$i" -eq 18 ]; then
        fail "ROOM_CAP mutation in log" "mutation not seen after 90s — check Pollinate:true and mutations goroutine (see step 3)"
    fi
done

echo ""
echo "════════════════════════════════════════════════════════════════════════"
echo "  All assertions passed. Your implementation is correct."
echo "════════════════════════════════════════════════════════════════════════"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Make it executable and run it:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;chmod +x test.sh
./test.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;If every assertion passes you see the final banner. If any assertion fails the script exits immediately with the step number to revisit.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 9 — Closing: What You Built and What Comes Next
&lt;/h2&gt;

&lt;p&gt;You started with an empty directory. You finished with a four-page Gin web application that admits at most 5 concurrent requests, queues the rest in FIFO order with a live-updating waiting room page, issues paid VIP passes that auto-promote returning clients for 90 minutes, and whose log file is provably safe to hand to a support engineer. Run &lt;code&gt;./test.sh&lt;/code&gt; one final time and watch every assertion pass.&lt;/p&gt;

&lt;p&gt;Take a moment to understand what each package actually did.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;room&lt;/code&gt; is not rate limiting. Rate limiting drops requests or returns 429. &lt;code&gt;room&lt;/code&gt; keeps them — every request gets a ticket, sees its position, and is admitted automatically when a slot opens. The reaper cleans up abandoned clients so ghost tickets never stall the queue. The promotion system with &lt;code&gt;PromoteTokenToFront&lt;/code&gt; and &lt;code&gt;GrantPass&lt;/code&gt; is a complete commercial primitive: you can charge per position, issue time-limited passes, and let the middleware handle the rest. Your handlers never change — they see normal requests arriving at the rate you configured.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;figtree&lt;/code&gt; is not a flag parser. It is a priority-ordered configuration resolver with live mutation tracking. The moment &lt;code&gt;ROOM_CAP&lt;/code&gt; changed in the environment, &lt;code&gt;wr.SetCap&lt;/code&gt; was called — no restart, no dropped requests, no intervention. In production that is how you respond to traffic spikes: your autoscaler updates the env var, figtree detects it, the room expands. The &lt;code&gt;RateFunc&lt;/code&gt; reading from &lt;code&gt;*figs.Float64(kRatePerPosition)&lt;/code&gt; on every call means your skip-the-line pricing can change live too.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;verbose&lt;/code&gt; is not just a logger. The moment &lt;code&gt;PromoteTokenToFront&lt;/code&gt; returned a &lt;code&gt;PassToken&lt;/code&gt;, that token's SHA-512 digest entered verbose's registry and every subsequent log line is scanned against it before it touches disk. The plaintext never persists. The rule you followed — log metadata, never the value or any substring — is the correct pattern for any secret: verbose is a safety net, not a substitute for keeping secrets out of log lines in the first place.&lt;/p&gt;




&lt;h3&gt;
  
  
  The packages used in this tutorial are part of a larger body of open source work
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;room&lt;/code&gt;, &lt;code&gt;figtree&lt;/code&gt;, and &lt;code&gt;verbose&lt;/code&gt; were all written by &lt;strong&gt;Andrei Merlescu&lt;/strong&gt; — &lt;a href="https://github.com/andreimerlescu" rel="noopener noreferrer"&gt;@andreimerlescu&lt;/a&gt; on GitHub. His profile carries 99 public repositories built across 17 years of professional software engineering spanning Cisco, Oracle, Warner Bros. Games, and SurgePays.&lt;/p&gt;

&lt;p&gt;Other packages worth exploring alongside the three you just used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/andreimerlescu/sema" rel="noopener noreferrer"&gt;&lt;code&gt;sema&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; — the semaphore that backs &lt;code&gt;room&lt;/code&gt;. Dynamic resizing, EWMA utilization tracking, context cancellation, drain/reset for maintenance windows. Zero-allocation hot path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/andreimerlescu/checkfs" rel="noopener noreferrer"&gt;&lt;code&gt;checkfs&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; — filesystem existence and permission checks. Pairs naturally with figtree when you want to validate that a config-supplied path actually exists before your server starts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;&lt;code&gt;lemmings&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; — load testing built around the concept of NPCs moving through your infrastructure as simulated users across geographic terrains and pack sizes. If you want to know what your waiting room does under real traffic before you ship it, lemmings is how you find out.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>devops</category>
      <category>logging</category>
      <category>infrastructure</category>
      <category>growth</category>
    </item>
    <item>
      <title>From Zero to Hero: Building a Key Issuance Server with `verbose` and `figtree`</title>
      <dc:creator>Andrei Merlescu</dc:creator>
      <pubDate>Mon, 20 Apr 2026 15:53:15 +0000</pubDate>
      <link>https://dev.to/andreimerlescu/from-zero-to-hero-building-a-key-issuance-server-with-verbose-and-figtree-npp</link>
      <guid>https://dev.to/andreimerlescu/from-zero-to-hero-building-a-key-issuance-server-with-verbose-and-figtree-npp</guid>
      <description>&lt;p&gt;In August 2024 Andrei Merlescu wrote a package called &lt;a href="https://github.com/andreimerlescu/verbose" rel="noopener noreferrer"&gt;verbose&lt;/a&gt; and released it under the Apache 2.0 license. The idea was novel: when interacting with sensitive information during multi-region deployments, you want to print to &lt;code&gt;STDOUT&lt;/code&gt; a censored expression of data while writing the unredacted form to log files — or in this tutorial's case, scrub secrets from the log file entirely. Private keys, passwords, serial numbers — these are sensitive and should not stream through STDOUT in devops contexts, especially in CI pipelines like GitHub Actions. With &lt;code&gt;verbose&lt;/code&gt;, your runtime can register new secrets as they become available, concurrently and safely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;go get &lt;span class="nt"&gt;-u&lt;/span&gt; github.com/andreimerlescu/verbose
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;aSecret&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="s"&gt;"abc123"&lt;/span&gt;
&lt;span class="c"&gt;// SecretBytes cast is required — AddSecret takes verbose.SecretBytes, not string&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SecretBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aSecret&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"***"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;stdoutLogger&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;log&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="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Stdout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LstdFlags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;// Tof takes a *log.Logger, not an io.Writer&lt;/span&gt;
&lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stdoutLogger&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"this is a secret: %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aSecret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Prints to &lt;code&gt;STDOUT&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;this is a secret: ***
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 1 — Scaffold and dependencies
&lt;/h2&gt;

&lt;p&gt;You are going to build a running HTTP server that issues, verifies, and revokes API keys. Every secret the server touches — the admin token that protects your endpoints, every key it issues — will be registered with the &lt;code&gt;verbose&lt;/code&gt; logging package the moment it is created. By the time you finish, you will be able to grep your own log file for any plaintext secret and find nothing. That is the goal.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;verbose&lt;/code&gt; is a logging package that scrubs registered secrets from every line it writes. &lt;code&gt;figtree&lt;/code&gt; is a configuration package that loads values from a YAML file, environment variables, and CLI flags in that priority order, with validators and mutation tracking built in.&lt;/p&gt;

&lt;p&gt;Create the project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;mkdir &lt;/span&gt;keyserver &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;cd &lt;/span&gt;keyserver
go mod init github.com/example/keyserver
go get github.com/andreimerlescu/verbose@latest
go get github.com/andreimerlescu/figtree/v2@latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the files you will fill in across the remaining steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;touch &lt;/span&gt;main.go config.yml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your &lt;code&gt;go.mod&lt;/code&gt; should now look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;module&lt;/span&gt; &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;keyserver&lt;/span&gt;

&lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="m"&gt;1.22&lt;/span&gt;

&lt;span class="n"&gt;require&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;andreimerlescu&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;figtree&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;v2&lt;/span&gt; &lt;span class="n"&gt;v2&lt;/span&gt;&lt;span class="m"&gt;.0.14&lt;/span&gt;
    &lt;span class="n"&gt;github&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;andreimerlescu&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;verbose&lt;/span&gt;    &lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="m"&gt;.2.0&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your directory:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;keyserver/
├── go.mod
├── go.sum
├── config.yml
└── main.go
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nothing runs yet. That is fine. You have a clean workspace and both packages are available.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 2 — CLI design with figtree
&lt;/h2&gt;

&lt;p&gt;Now that the workspace is ready, the first thing to design is configuration. Before any HTTP server, any logger, any route — you need to know what the program accepts and where it reads it from. figtree handles all three sources — file, environment, flags — in one tree.&lt;/p&gt;

&lt;p&gt;Every config key is a Go constant. This is not optional style — it prevents typos from silently creating a second key that is never read.&lt;/p&gt;

&lt;p&gt;Add this to &lt;code&gt;main.go&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// main.go — complete file at this stage&lt;/span&gt;

&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/andreimerlescu/figtree/v2"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// config keys — always constants, never raw strings at call sites&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;kHost&lt;/span&gt;          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"host"&lt;/span&gt;
    &lt;span class="n"&gt;kPort&lt;/span&gt;          &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"port"&lt;/span&gt;
    &lt;span class="n"&gt;kLogDir&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"log-dir"&lt;/span&gt;
    &lt;span class="n"&gt;kTruncate&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"truncate"&lt;/span&gt;
    &lt;span class="n"&gt;kKeyPrefix&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"key-prefix"&lt;/span&gt;
    &lt;span class="n"&gt;kAdminToken&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"keyserver-admin-token"&lt;/span&gt; &lt;span class="c"&gt;// env: KEYSERVER_ADMIN_TOKEN&lt;/span&gt;
    &lt;span class="n"&gt;kOnCall&lt;/span&gt;        &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"on-call-engineer"&lt;/span&gt;      &lt;span class="c"&gt;// env: ON_CALL_ENGINEER&lt;/span&gt;
    &lt;span class="n"&gt;kShiftDuration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"shift-duration"&lt;/span&gt;        &lt;span class="c"&gt;// env: SHIFT_DURATION&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// Grow a tracked tree — Tracking enables the Mutations() channel,&lt;/span&gt;
    &lt;span class="c"&gt;// Pollinate re-checks env vars on every Getter call (needed for Step 7),&lt;/span&gt;
    &lt;span class="c"&gt;// ConfigFile makes the intent explicit: this app is config-file-first.&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;figtree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;With&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figtree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Tracking&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Germinate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c"&gt;// ignore -test.* flags during go test&lt;/span&gt;
        &lt;span class="n"&gt;Pollinate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c"&gt;// re-read env vars on every Getter call&lt;/span&gt;
        &lt;span class="n"&gt;ConfigFile&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"./config.yml"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c"&gt;// -- server --&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kHost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"127.0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"host address to listen on"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kPort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"port to listen on"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// -- logging --&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kLogDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"./logs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"directory to write log files into"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewBool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kTruncate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"truncate log file on each run"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// -- keys --&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kKeyPrefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ks_"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"prefix for issued API keys"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// RuleNoFlags means figtree will never register a CLI flag for this key.&lt;/span&gt;
    &lt;span class="c"&gt;// The only way to supply it is via KEYSERVER_ADMIN_TOKEN or config.yml.&lt;/span&gt;
    &lt;span class="c"&gt;// The key name is deliberately long — verbose by design.&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kAdminToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"admin token — use KEYSERVER_ADMIN_TOKEN env var only"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithRule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kAdminToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;figtree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RuleNoFlags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// -- on-call rotation --&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kOnCall&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Darron"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"name of the engineer currently on call"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// shift-duration drives the rotation goroutine added in Step 3.&lt;/span&gt;
    &lt;span class="c"&gt;// Default is 7 hours. SHIFT_DURATION=1 overrides it for local testing.&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewUnitDuration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kShiftDuration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"length of one on-call shift in hours"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// Problems() catches developer mistakes — duplicate keys, bad validator&lt;/span&gt;
    &lt;span class="c"&gt;// combos — before Load() runs. These are your bugs, not user input errors.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;problems&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Problems&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;problems&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&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;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;problems&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&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;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"figtree problem: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&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;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Load resolves: config.yml → env vars → CLI flags (in that priority order)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&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;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"figtree.Load: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&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;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// Print everything so you can see what loaded — fmt.Println for now,&lt;/span&gt;
    &lt;span class="c"&gt;// verbose replaces this in Step 4.&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"host=%s port=%d log-dir=%s truncate=%v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&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;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kHost&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kPort&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kLogDir&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kTruncate&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"key-prefix=%s on-call=%s shift=%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&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;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kKeyPrefix&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kOnCall&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kShiftDuration&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"admin-token=%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&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;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kAdminToken&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create &lt;code&gt;config.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;host&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;             &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1"&lt;/span&gt;
&lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;             &lt;span class="m"&gt;8080&lt;/span&gt;
&lt;span class="na"&gt;log-dir&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;./logs"&lt;/span&gt;
&lt;span class="na"&gt;truncate&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;         &lt;span class="kc"&gt;false&lt;/span&gt;
&lt;span class="na"&gt;key-prefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;       &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ks_"&lt;/span&gt;
&lt;span class="na"&gt;on-call-engineer&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Darron"&lt;/span&gt;
&lt;span class="na"&gt;shift-duration&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;   &lt;span class="m"&gt;7&lt;/span&gt;
&lt;span class="c1"&gt;# keyserver-admin-token is intentionally absent from source control.&lt;/span&gt;
&lt;span class="c1"&gt;# Set it via: export KEYSERVER_ADMIN_TOKEN=your-secret&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;KEYSERVER_ADMIN_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mysecret go run &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see all values printed. Notice &lt;code&gt;admin-token=mysecret&lt;/code&gt; is in your terminal output right now. That is expected — verbose is not wired yet. You will fix that in Step 4 and never see it in plaintext again.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mistake trap:&lt;/strong&gt; Try running &lt;code&gt;go run . -keyserver-admin-token=mysecret&lt;/code&gt;. figtree will not register that flag because of &lt;code&gt;RuleNoFlags&lt;/code&gt; — the flag is unknown to the flag package and your program will exit with an error. The env var is the only valid path. This is intentional.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 3 — Validators, Problems(), and mutations
&lt;/h2&gt;

&lt;p&gt;Now that the tree is declared, you lock it down. Validators reject bad input before your program does anything with it. The &lt;code&gt;Problems()&lt;/code&gt; check you already have catches declaration mistakes. The mutations goroutine watches for runtime changes — which is where the on-call rotation lives.&lt;/p&gt;

&lt;p&gt;Add these diffs to &lt;code&gt;main.go&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;After &lt;code&gt;figs.NewString(kHost, ...)&lt;/code&gt; — add validator:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="c"&gt;// 3 lines above&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kHost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"127.0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"host address to listen on"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// add this&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kHost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;figtree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AssureStringNotEmpty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// 3 lines below&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kPort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"port to listen on"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After &lt;code&gt;figs.NewInt(kPort, ...)&lt;/code&gt; — add validator:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="c"&gt;// 3 lines above&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kPort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"port to listen on"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// add this&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kPort&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;figtree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AssureIntInRange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;65535&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c"&gt;// 3 lines below&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kLogDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"./logs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"directory to write log files into"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After &lt;code&gt;figs.NewString(kLogDir, ...)&lt;/code&gt; — add validator:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="c"&gt;// 3 lines above&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kLogDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"./logs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"directory to write log files into"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// add this&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kLogDir&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;figtree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AssureStringNotEmpty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// 3 lines below&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewBool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kTruncate&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"truncate log file on each run"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After &lt;code&gt;figs.NewString(kKeyPrefix, ...)&lt;/code&gt; — add validator:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="c"&gt;// 3 lines above&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kKeyPrefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"ks_"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"prefix for issued API keys"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// add this&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kKeyPrefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;figtree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AssureStringNotEmpty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// 3 lines below&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kAdminToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"admin token — use KEYSERVER_ADMIN_TOKEN env var only"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After &lt;code&gt;figs.WithRule(kAdminToken, ...)&lt;/code&gt; — add validator:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="c"&gt;// 3 lines above&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithRule&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kAdminToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;figtree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RuleNoFlags&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// add this — admin token must be present at startup&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kAdminToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;figtree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AssureStringNotEmpty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// 3 lines below&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kOnCall&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Darron"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"name of the engineer currently on call"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After &lt;code&gt;figs.NewString(kOnCall, ...)&lt;/code&gt; — add validator:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="c"&gt;// 3 lines above&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kOnCall&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Darron"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"name of the engineer currently on call"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// add this&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kOnCall&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;figtree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AssureStringNotEmpty&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// 3 lines below&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewUnitDuration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kShiftDuration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"length of one on-call shift in hours"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;After &lt;code&gt;figs.NewUnitDuration(kShiftDuration, ...)&lt;/code&gt; — add validators:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="c"&gt;// 3 lines above&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewUnitDuration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kShiftDuration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hour&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"length of one on-call shift in hours"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// add these — shifts must be at least 1 hour, at most 12&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kShiftDuration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;figtree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AssureDurationMin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hour&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithValidator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kShiftDuration&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;figtree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AssureDurationMax&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;12&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hour&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c"&gt;// 3 lines below&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;problems&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Problems&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;problems&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now add the mutations goroutine and the on-call shift rotation. Both go &lt;strong&gt;after&lt;/strong&gt; &lt;code&gt;figs.Load()&lt;/code&gt; — the tree must be live before you watch it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="c"&gt;// 3 lines above&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Load&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&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;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"figtree.Load: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&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;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c"&gt;// add this block&lt;/span&gt;
    &lt;span class="c"&gt;// watch for any config value changing at runtime and log it&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&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;m&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mutations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c"&gt;// verbose.Printf replaces this in Step 4&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"config mutation: %s changed from %v to %v at %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&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="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;When&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;

    &lt;span class="c"&gt;// on-call rotation — fires every minute, updates kOnCall based on&lt;/span&gt;
    &lt;span class="c"&gt;// the current hour. We check every minute so the change is never&lt;/span&gt;
    &lt;span class="c"&gt;// more than 60 seconds late.&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Minute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StoreString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kOnCall&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;onCallEngineer&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;
    &lt;span class="c"&gt;// 3 lines below&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"host=%s port=%d log-dir=%s truncate=%v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the &lt;code&gt;onCallEngineer&lt;/code&gt; function at the bottom of &lt;code&gt;main.go&lt;/code&gt;, outside &lt;code&gt;main&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// onCallEngineer returns the name of the engineer on call right now.&lt;/span&gt;
&lt;span class="c"&gt;//&lt;/span&gt;
&lt;span class="c"&gt;//   04:00–07:59  → TEAM     (everyone expected online)&lt;/span&gt;
&lt;span class="c"&gt;//   08:00–14:59  → Darron&lt;/span&gt;
&lt;span class="c"&gt;//   15:00–21:59  → Bradley&lt;/span&gt;
&lt;span class="c"&gt;//   22:00–03:59  → Kevin    (crosses midnight)&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;onCallEngineer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Hour&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"TEAM"&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Darron"&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;22&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Bradley"&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;// 22:00–03:59&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Kevin"&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it again:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;KEYSERVER_ADMIN_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mysecret go run &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To prove the validator works, temporarily set &lt;code&gt;port: 99999&lt;/code&gt; in &lt;code&gt;config.yml&lt;/code&gt; and run again. You will see figtree refuse to load with a clear error. Set it back to &lt;code&gt;8080&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mistake trap:&lt;/strong&gt; Move the mutations goroutine to before &lt;code&gt;figs.Load()&lt;/code&gt; and run it. The channel exists but the tree has not resolved its values yet — mutations fired during load will not be seen because the goroutine may not be scheduled before load completes. Always start the mutations goroutine after &lt;code&gt;Load()&lt;/code&gt; returns.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 4 — Wire verbose, replace fmt, register the admin token
&lt;/h2&gt;

&lt;p&gt;Now that figtree is loading and validating configuration correctly, you can wire up the logger. verbose needs to be initialised before any log call — and the admin token needs to be registered as a secret immediately after, before anything else in the program touches it.&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;verbose&lt;/code&gt; to your imports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;
    &lt;span class="c"&gt;// add this&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/andreimerlescu/verbose"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/andreimerlescu/figtree/v2"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the verbose initialisation block right after the goroutines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="c"&gt;// 3 lines above&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tick&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Minute&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StoreString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kOnCall&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;onCallEngineer&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;
    &lt;span class="c"&gt;// add this block&lt;/span&gt;
    &lt;span class="c"&gt;// initialise verbose before any log call — guard() will stderr and no-op&lt;/span&gt;
    &lt;span class="c"&gt;// if you call verbose functions before this point&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewLogger&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Dir&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kLogDir&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="s"&gt;"keyserver"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Truncate&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kTruncate&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;DirMode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;  &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;o755&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;FileMode&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="n"&gt;o640&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&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;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"verbose.NewLogger: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&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;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// read the admin token once — this is the value we protect at startup&lt;/span&gt;
    &lt;span class="n"&gt;adminToken&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kAdminToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// register the admin token as a verbose secret immediately.&lt;/span&gt;
    &lt;span class="c"&gt;// verbose stores only the SHA-512 digest — the plaintext is never retained.&lt;/span&gt;
    &lt;span class="c"&gt;// every log line written after this point that contains the raw token value&lt;/span&gt;
    &lt;span class="c"&gt;// will have it replaced with [ADMIN_TOKEN] automatically.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SecretBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;adminToken&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"[ADMIN_TOKEN]"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&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;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"verbose.AddSecret: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&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;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c"&gt;// proof that scrubbing is active — this line contains the raw token.&lt;/span&gt;
    &lt;span class="c"&gt;// open logs/keyserver.log after running and you will see [ADMIN_TOKEN].&lt;/span&gt;
    &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"admin token registered — value in log: %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;adminToken&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// Version() is a function call, not a variable&lt;/span&gt;
    &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"keyserver starting (verbose v%s / figtree v%s)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;VERSION&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;figtree&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Version&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="c"&gt;// 3 lines below&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"host=%s port=%d log-dir=%s truncate=%v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the &lt;code&gt;fmt.Printf&lt;/code&gt; startup summary:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="c"&gt;// remove this&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"host=%s port=%d log-dir=%s truncate=%v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&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;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kHost&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kPort&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kLogDir&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kTruncate&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"key-prefix=%s on-call=%s shift=%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&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;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kKeyPrefix&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kOnCall&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kShiftDuration&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"admin-token=%s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&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;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kAdminToken&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="c"&gt;// replace with this&lt;/span&gt;
    &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"host=%s port=%d log-dir=%s truncate=%v key-prefix=%s on-call=%s shift=%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kHost&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kPort&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kLogDir&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kTruncate&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kKeyPrefix&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kOnCall&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kShiftDuration&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the &lt;code&gt;fmt.Printf&lt;/code&gt; in the mutations goroutine:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="c"&gt;// 3 lines above&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&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;m&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mutations&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c"&gt;// remove this&lt;/span&gt;
            &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"config mutation: %s changed from %v to %v at %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&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="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;When&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="c"&gt;// replace with this&lt;/span&gt;
            &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"config mutation: %s changed from %v to %v at %s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Property&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Old&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&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="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;When&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;KEYSERVER_ADMIN_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mysecret go run &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open &lt;code&gt;logs/keyserver.log&lt;/code&gt;. Find the line containing "admin token registered". The value &lt;code&gt;mysecret&lt;/code&gt; does not appear — you will see &lt;code&gt;[ADMIN_TOKEN]&lt;/code&gt; in its place. From this point forward, &lt;code&gt;mysecret&lt;/code&gt; cannot appear in any log line this process writes.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mistake trap:&lt;/strong&gt; Call &lt;code&gt;verbose.Printf("test")&lt;/code&gt; before &lt;code&gt;verbose.NewLogger&lt;/code&gt; — move it above the &lt;code&gt;NewLogger&lt;/code&gt; block and run. You will see the message printed to stderr with a "NewLogger or SetLogger has not been called" prefix. verbose does not panic — it fails open to stderr. Move the call back below &lt;code&gt;NewLogger&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 5 — HTTP server skeleton
&lt;/h2&gt;

&lt;p&gt;With logging and configuration solid, the server can be wired up. The three routes are stubs for now — each returns &lt;code&gt;200 OK&lt;/code&gt; with a placeholder body. The &lt;code&gt;requireAdmin&lt;/code&gt; middleware is the important piece here: it logs every auth attempt, which means the admin token will be scrubbed from those log lines automatically.&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;net/http&lt;/code&gt; and &lt;code&gt;encoding/json&lt;/code&gt; to your imports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;
    &lt;span class="c"&gt;// add these&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/json"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/andreimerlescu/verbose"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/andreimerlescu/figtree/v2"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the server block at the end of &lt;code&gt;main&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="c"&gt;// 3 lines above&lt;/span&gt;
    &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"host=%s port=%d log-dir=%s truncate=%v key-prefix=%s on-call=%s shift=%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kHost&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kPort&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kLogDir&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kTruncate&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kKeyPrefix&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kOnCall&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kShiftDuration&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c"&gt;// add this block&lt;/span&gt;
    &lt;span class="c"&gt;// capture prefix once — used in handlers added in Step 6&lt;/span&gt;
    &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kKeyPrefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;

    &lt;span class="c"&gt;// POST /keys — issue a new API key (admin only)&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST /keys"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requireAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;adminToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)&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;NewEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"not implemented"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;

    &lt;span class="c"&gt;// GET /keys/{id}/verify — verify a presented key&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GET /keys/{id}/verify"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)&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;NewEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s"&gt;"status"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"not implemented"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="c"&gt;// DELETE /keys/{id} — revoke and remove a key (admin only)&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DELETE /keys/{id}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requireAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;adminToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusNoContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;

    &lt;span class="n"&gt;addr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sprintf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%s:%d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kHost&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;figs&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kPort&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"keyserver listening on http://%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;addr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c"&gt;// ListenAndServe only returns on error — log it with a trace and exit&lt;/span&gt;
        &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fprintf&lt;/span&gt;&lt;span class="p"&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;Stderr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"http.ListenAndServe: %v&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TracefReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ListenAndServe failed: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&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;Exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c"&gt;// 3 lines below — end of main&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;requireAdmin&lt;/code&gt; and &lt;code&gt;bearerToken&lt;/code&gt; below &lt;code&gt;onCallEngineer&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// requireAdmin rejects requests whose Bearer token does not match adminToken.&lt;/span&gt;
&lt;span class="c"&gt;// The token is already a verbose secret — the log line is safe.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;requireAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;adminToken&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;presented&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bearerToken&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="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%s %s — auth attempt from %s token=%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&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 class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RemoteAddr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;presented&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;presented&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;adminToken&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"%s %s — FORBIDDEN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;URL&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 class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"forbidden"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusForbidden&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// bearerToken extracts the token from Authorization: Bearer &amp;lt;token&amp;gt;&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;bearerToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&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;auth&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;7&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;"Bearer "&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;7&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="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run the server and curl the stubs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;KEYSERVER_ADMIN_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mysecret go run &lt;span class="nb"&gt;.&lt;/span&gt; &amp;amp;

curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST http://localhost:8080/keys &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer mysecret"&lt;/span&gt;

curl &lt;span class="nt"&gt;-s&lt;/span&gt; http://localhost:8080/keys/test/verify

curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; DELETE http://localhost:8080/keys/test &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer mysecret"&lt;/span&gt;

&lt;span class="nb"&gt;kill&lt;/span&gt; %1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Check &lt;code&gt;logs/keyserver.log&lt;/code&gt;. The auth lines show &lt;code&gt;token=[ADMIN_TOKEN]&lt;/code&gt; — never &lt;code&gt;mysecret&lt;/code&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mistake trap:&lt;/strong&gt; If you are on Go below 1.22, &lt;code&gt;http.HandleFunc("POST /keys", ...)&lt;/code&gt; will not compile — the method prefix in the pattern is a 1.22 feature. Run &lt;code&gt;go version&lt;/code&gt; to check. If needed, update your toolchain: &lt;code&gt;go get toolchain@go1.22.0&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 6 — Issuance, verification, revocation, and runtime secret registration
&lt;/h2&gt;

&lt;p&gt;This is the core of the application and the core demonstration of verbose. The moment a key is generated, &lt;code&gt;verbose.AddSecret&lt;/code&gt; is called before anything else — before the key is stored, before it is logged, before it is returned to the caller. The window between generation and registration is zero.&lt;/p&gt;

&lt;p&gt;When a key is deleted, &lt;code&gt;verbose.RemoveSecret&lt;/code&gt; is called. This is safe because a deleted key is dead — no future request will present it, so there is no future log line to protect. Removing it keeps the verbose runtime lean: the secrets registry does not grow forever, and scanning each log line is O(n) in the number of registered secrets. In a long-running server issuing thousands of keys, this matters. The same pattern applies to user sessions: register the session token on login, remove it on logout.&lt;/p&gt;

&lt;p&gt;Add the key store above &lt;code&gt;main&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// 3 lines above — imports&lt;/span&gt;
&lt;span class="c"&gt;// add this block after imports, before main&lt;/span&gt;

&lt;span class="c"&gt;// keyRecord holds metadata for one issued API key.&lt;/span&gt;
&lt;span class="c"&gt;// The token value is the map key — never stored inside the struct.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;keyRecord&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ID&lt;/span&gt;       &lt;span class="kt"&gt;string&lt;/span&gt;    &lt;span class="s"&gt;`json:"id"`&lt;/span&gt;
    &lt;span class="n"&gt;IssuedAt&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Time&lt;/span&gt; &lt;span class="s"&gt;`json:"issued_at"`&lt;/span&gt;
    &lt;span class="n"&gt;IssuedTo&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;    &lt;span class="s"&gt;`json:"issued_to"`&lt;/span&gt;
    &lt;span class="n"&gt;Revoked&lt;/span&gt;  &lt;span class="kt"&gt;bool&lt;/span&gt;      &lt;span class="s"&gt;`json:"revoked"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;keyStoreMu&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RWMutex&lt;/span&gt;
    &lt;span class="n"&gt;keyStore&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&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="n"&gt;keyRecord&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;// 3 lines below — func main() {&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;sync&lt;/code&gt;, &lt;code&gt;crypto/rand&lt;/code&gt;, and &lt;code&gt;encoding/hex&lt;/code&gt; to imports:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="c"&gt;// add to import block&lt;/span&gt;
    &lt;span class="s"&gt;"crypto/rand"&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/hex"&lt;/span&gt;
    &lt;span class="s"&gt;"sync"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the &lt;code&gt;POST /keys&lt;/code&gt; stub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="c"&gt;// 3 lines above&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST /keys"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requireAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;adminToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// replace body&lt;/span&gt;
        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;IssuedTo&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"issued_to"`&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&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;NewDecoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IssuedTo&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST /keys — bad request from %s: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RemoteAddr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"issued_to is required"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusBadRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;generateToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c"&gt;// TracefReturn logs with a stack trace and returns the error —&lt;/span&gt;
            &lt;span class="c"&gt;// capture the return value, do not discard it&lt;/span&gt;
            &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST /keys — token generation failed: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TracefReturn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"generateToken: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"internal error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// register with verbose FIRST — before store, before log, before response.&lt;/span&gt;
        &lt;span class="c"&gt;// after this line the token cannot appear in plaintext in any log output.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AddSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SecretBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"[ISSUED_KEY]"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST /keys — failed to protect token: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"internal error"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;rec&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;keyRecord&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;ID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;IssuedAt&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;UTC&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
            &lt;span class="n"&gt;IssuedTo&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IssuedTo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;keyStoreMu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;keyStore&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;
        &lt;span class="n"&gt;keyStoreMu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c"&gt;// safe to log — token is already scrubbed to [ISSUED_KEY]&lt;/span&gt;
        &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST /keys — issued key=%s to=%s at=%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IssuedTo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IssuedAt&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="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RFC3339&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCreated&lt;/span&gt;&lt;span class="p"&gt;)&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;NewEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"token"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"issued_to"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IssuedTo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"issued_at"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IssuedAt&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="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RFC3339&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="c"&gt;// 3 lines below&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the &lt;code&gt;GET /keys/{id}/verify&lt;/code&gt; stub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="c"&gt;// 3 lines above&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GET /keys/{id}/verify"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// replace body&lt;/span&gt;
        &lt;span class="n"&gt;presented&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bearerToken&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="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PathValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c"&gt;// log the attempt — if the caller sent a valid token it is already&lt;/span&gt;
        &lt;span class="c"&gt;// a verbose secret and will be scrubbed automatically&lt;/span&gt;
        &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GET /keys/%s/verify — attempt from %s token=%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RemoteAddr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;presented&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;keyStoreMu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RLock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;keyStore&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;presented&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;keyStoreMu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RUnlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Revoked&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GET /keys/%s/verify — INVALID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"invalid or revoked key"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusUnauthorized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GET /keys/%s/verify — VALID issued_to=%s"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IssuedTo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)&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;NewEncoder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;map&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="n"&gt;any&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="s"&gt;"valid"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"issued_to"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IssuedTo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="s"&gt;"issued_at"&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IssuedAt&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="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RFC3339&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="c"&gt;// 3 lines below&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace the &lt;code&gt;DELETE /keys/{id}&lt;/code&gt; stub:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;    &lt;span class="c"&gt;// 3 lines above&lt;/span&gt;
    &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DELETE /keys/{id}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requireAdmin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;adminToken&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// replace body&lt;/span&gt;
        &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PathValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;keyStoreMu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;keyStore&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Revoked&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;keyStoreMu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;exists&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DELETE /keys/%s — not found"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"key not found"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusNotFound&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// the key is dead — safe to remove from verbose.&lt;/span&gt;
        &lt;span class="c"&gt;// removing it keeps the secrets registry lean: verbose scans every&lt;/span&gt;
        &lt;span class="c"&gt;// log line against every registered secret, so a smaller registry&lt;/span&gt;
        &lt;span class="c"&gt;// means faster logging. do not leave dead secrets registered forever.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RemoveSecret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SecretBytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DELETE /keys/%s — RemoveSecret error: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;verbose&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DELETE /keys/%s — revoked and removed from verbose (was issued to %s)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;IssuedTo&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusNoContent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}))&lt;/span&gt;
    &lt;span class="c"&gt;// 3 lines below&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;generateToken&lt;/code&gt; below &lt;code&gt;bearerToken&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// generateToken returns a cryptographically random token with the given prefix.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;generateToken&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&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 class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&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="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;hex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EncodeToString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mistake trap:&lt;/strong&gt; Move &lt;code&gt;verbose.AddSecret&lt;/code&gt; to after &lt;code&gt;keyStoreMu.Lock()&lt;/code&gt; — just two lines later — and run the server. Issue a key. Look at the log line for &lt;code&gt;POST /keys — issued key=&lt;/code&gt;. You will see the raw token value in plaintext because it was logged by &lt;code&gt;requireAdmin&lt;/code&gt; in the auth check before it was registered. Order is not a style preference here. It is the security guarantee. Move &lt;code&gt;AddSecret&lt;/code&gt; back to first.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 7 — The on-call engineer shift in action
&lt;/h2&gt;

&lt;p&gt;The rotation goroutine has been running since Step 3, quietly updating a value. Now that verbose is wired, the mutation log entries are meaningful — and with &lt;code&gt;Pollinate: true&lt;/code&gt;, an engineer can override the on-call value live from a second terminal without restarting the server.&lt;/p&gt;

&lt;p&gt;The shift goroutine is already in place. The mutations goroutine is already using &lt;code&gt;verbose.Printf&lt;/code&gt; from Step 4. Run the server and open a second terminal:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# terminal 1&lt;/span&gt;
&lt;span class="nv"&gt;KEYSERVER_ADMIN_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;mysecret go run &lt;span class="nb"&gt;.&lt;/span&gt;

&lt;span class="c"&gt;# terminal 2 — override the on-call engineer without restarting&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ON_CALL_ENGINEER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;Kevin
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Watch &lt;code&gt;logs/keyserver.log&lt;/code&gt; — within one minute you will see:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight console"&gt;&lt;code&gt;&lt;span class="go"&gt;[VERBOSE] keyserver.go:XX: config mutation: on-call-engineer changed from Darron to Kevin at 2026-04-20...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The mutation fires because &lt;code&gt;Pollinate: true&lt;/code&gt; causes figtree to re-read &lt;code&gt;ON_CALL_ENGINEER&lt;/code&gt; every time &lt;code&gt;figs.String(kOnCall)&lt;/code&gt; is called — which the rotation goroutine does every minute.&lt;/p&gt;

&lt;p&gt;If the on-call engineer name were a sensitive internal codename — a security handle, a contractor alias, anything you would not want in a log shipped to an aggregator — one &lt;code&gt;verbose.AddSecret(verbose.SecretBytes(name), "[ON_CALL]")&lt;/code&gt; call in the rotation goroutine would scrub it from every future log line. The pattern is identical to what you did with the admin token.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Mistake trap:&lt;/strong&gt; Remove &lt;code&gt;Pollinate: true&lt;/code&gt; from &lt;code&gt;figtree.Options&lt;/code&gt;, restart the server, and set &lt;code&gt;export ON_CALL_ENGINEER=Kevin&lt;/code&gt; again. No mutation fires. The env var changed but figtree did not re-read it because Pollinate was off. Put &lt;code&gt;Pollinate: true&lt;/code&gt; back.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 8 — The bash test script
&lt;/h2&gt;

&lt;p&gt;Create &lt;code&gt;test.sh&lt;/code&gt; in the project root. This script is the pass/fail gate for the tutorial. If your implementation is correct it exits &lt;code&gt;0&lt;/code&gt;. Each assertion failure prints which step to revisit and exits immediately.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="c"&gt;# ── dependency check ──────────────────────────────────────────────────────────&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; curl &amp;amp;&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ERROR: curl is required. Install it and rerun."&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi
if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; jq &amp;amp;&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"ERROR: jq is required."&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  macOS:  brew install jq"&lt;/span&gt;
    &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  Linux:  sudo apt install jq  OR  sudo yum install jq"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# ── config ────────────────────────────────────────────────────────────────────&lt;/span&gt;
&lt;span class="nv"&gt;ADMIN_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"test-admin-secret-1234"&lt;/span&gt;
&lt;span class="nv"&gt;BASE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"http://127.0.0.1:8080"&lt;/span&gt;
&lt;span class="nv"&gt;LOG_FILE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"./logs/keyserver.log"&lt;/span&gt;

&lt;span class="c"&gt;# ── helpers ───────────────────────────────────────────────────────────────────&lt;/span&gt;
pass&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  PASS: &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
fail&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  FAIL: &lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt; — &lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

assert_status&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;expected&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;actual&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;step&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$4&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$actual&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-ne&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$expected&lt;/span&gt;&lt;span class="s2"&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;then
        &lt;/span&gt;fail &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$label&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"expected HTTP &lt;/span&gt;&lt;span class="nv"&gt;$expected&lt;/span&gt;&lt;span class="s2"&gt;, got &lt;/span&gt;&lt;span class="nv"&gt;$actual&lt;/span&gt;&lt;span class="s2"&gt; (see step &lt;/span&gt;&lt;span class="nv"&gt;$step&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;
    &lt;span class="k"&gt;fi
    &lt;/span&gt;pass &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$label&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

assert_no_plaintext&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;secret&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;step&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qF&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$secret&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;fail &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$label&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"plaintext secret found in log — verbose.AddSecret not called before logging (see step &lt;/span&gt;&lt;span class="nv"&gt;$step&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;
    &lt;span class="k"&gt;fi
    &lt;/span&gt;pass &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$label&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

assert_log_contains&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;local &lt;/span&gt;&lt;span class="nv"&gt;label&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;pattern&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$2&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nv"&gt;step&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$3&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qF&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$pattern&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;fail &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$label&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"expected '&lt;/span&gt;&lt;span class="nv"&gt;$pattern&lt;/span&gt;&lt;span class="s2"&gt;' in log — not found (see step &lt;/span&gt;&lt;span class="nv"&gt;$step&lt;/span&gt;&lt;span class="s2"&gt;)"&lt;/span&gt;
    &lt;span class="k"&gt;fi
    &lt;/span&gt;pass &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$label&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# ── build and start server ────────────────────────────────────────────────────&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=&amp;gt; building..."&lt;/span&gt;
go build &lt;span class="nt"&gt;-o&lt;/span&gt; keyserver_bin &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Build failed — fix compilation errors first."&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"=&amp;gt; starting server..."&lt;/span&gt;
&lt;span class="nv"&gt;KEYSERVER_ADMIN_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; ./keyserver_bin &amp;amp;
&lt;span class="nv"&gt;SERVER_PID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$!&lt;/span&gt;
&lt;span class="nb"&gt;trap&lt;/span&gt; &lt;span class="s1"&gt;'kill $SERVER_PID 2&amp;gt;/dev/null; rm -f keyserver_bin'&lt;/span&gt; EXIT

&lt;span class="c"&gt;# wait for server to be ready — retry up to 10 times&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;seq &lt;/span&gt;1 10&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    if &lt;/span&gt;curl &lt;span class="nt"&gt;-sf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/keys/test/verify"&lt;/span&gt; &amp;amp;&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;&lt;span class="nb"&gt;break
    &lt;/span&gt;&lt;span class="k"&gt;fi
    &lt;/span&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;0.5
&lt;span class="k"&gt;done

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"── assertion 1: admin token is scrubbed from logs (Step 4) ─────────────"&lt;/span&gt;
assert_no_plaintext &lt;span class="s2"&gt;"admin token not in log"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"4"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"── assertion 2: issue key for alice (Step 6) ────────────────────────────"&lt;/span&gt;
&lt;span class="nv"&gt;ALICE_RESP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;%{http_code}"&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/keys"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"issued_to":"alice@example.com"}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;ALICE_STATUS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ALICE_RESP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;ALICE_BODY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ALICE_RESP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
assert_status &lt;span class="s2"&gt;"POST /keys alice"&lt;/span&gt; 201 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ALICE_STATUS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"6"&lt;/span&gt;
&lt;span class="nv"&gt;ALICE_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ALICE_BODY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; .token&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"── assertion 3: issue key for bob (Step 6) ──────────────────────────────"&lt;/span&gt;
&lt;span class="nv"&gt;BOB_RESP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;%{http_code}"&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/keys"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"issued_to":"bob@example.com"}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;BOB_STATUS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BOB_RESP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;BOB_BODY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BOB_RESP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
assert_status &lt;span class="s2"&gt;"POST /keys bob"&lt;/span&gt; 201 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BOB_STATUS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"6"&lt;/span&gt;
&lt;span class="nv"&gt;BOB_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BOB_BODY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; .token&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"── assertion 4: issue key for carol (Step 6) ────────────────────────────"&lt;/span&gt;
&lt;span class="nv"&gt;CAROL_RESP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s2"&gt;%{http_code}"&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/keys"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{"issued_to":"carol@example.com"}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;CAROL_STATUS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CAROL_RESP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tail&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;CAROL_BODY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CAROL_RESP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;head&lt;/span&gt; &lt;span class="nt"&gt;-1&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
assert_status &lt;span class="s2"&gt;"POST /keys carol"&lt;/span&gt; 201 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CAROL_STATUS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"6"&lt;/span&gt;
&lt;span class="nv"&gt;CAROL_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CAROL_BODY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; .token&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"── assertion 5: verify alice (Step 6) ───────────────────────────────────"&lt;/span&gt;
&lt;span class="nv"&gt;STATUS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/keys/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;ALICE_TOKEN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/verify"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$ALICE_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
assert_status &lt;span class="s2"&gt;"GET /keys alice/verify"&lt;/span&gt; 200 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATUS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"6"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"── assertion 6: verify bob (Step 6) ─────────────────────────────────────"&lt;/span&gt;
&lt;span class="nv"&gt;STATUS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/keys/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BOB_TOKEN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/verify"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$BOB_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
assert_status &lt;span class="s2"&gt;"GET /keys bob/verify"&lt;/span&gt; 200 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATUS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"6"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"── assertion 7: verify carol (Step 6) ───────────────────────────────────"&lt;/span&gt;
&lt;span class="nv"&gt;STATUS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/keys/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CAROL_TOKEN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/verify"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$CAROL_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
assert_status &lt;span class="s2"&gt;"GET /keys carol/verify"&lt;/span&gt; 200 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATUS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"6"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"── assertion 8: revoke bob (Step 6) ─────────────────────────────────────"&lt;/span&gt;
&lt;span class="nv"&gt;STATUS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; DELETE &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/keys/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BOB_TOKEN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$ADMIN_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
assert_status &lt;span class="s2"&gt;"DELETE /keys bob"&lt;/span&gt; 204 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATUS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"6"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"── assertion 9: verify bob after revocation — expect 401 (Step 6) ───────"&lt;/span&gt;
&lt;span class="nv"&gt;STATUS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;curl &lt;span class="nt"&gt;-s&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; /dev/null &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"%{http_code}"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BASE&lt;/span&gt;&lt;span class="s2"&gt;/keys/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;BOB_TOKEN&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/verify"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer &lt;/span&gt;&lt;span class="nv"&gt;$BOB_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
assert_status &lt;span class="s2"&gt;"GET /keys bob/verify after revoke"&lt;/span&gt; 401 &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$STATUS&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"6"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"── assertion 10: alice and carol tokens not in log (Step 6) ─────────────"&lt;/span&gt;
assert_no_plaintext &lt;span class="s2"&gt;"alice token not in log"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ALICE_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"6"&lt;/span&gt;
assert_no_plaintext &lt;span class="s2"&gt;"carol token not in log"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$CAROL_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"6"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"── assertion 11: bob token not in log (Step 6) ──────────────────────────"&lt;/span&gt;
&lt;span class="c"&gt;# bob's token was removed via RemoveSecret after revocation.&lt;/span&gt;
&lt;span class="c"&gt;# if the implementation is correct the token never appeared in plaintext&lt;/span&gt;
&lt;span class="c"&gt;# because AddSecret was called before any logging.&lt;/span&gt;
assert_no_plaintext &lt;span class="s2"&gt;"bob token not in log"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BOB_TOKEN&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="s2"&gt;"6"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"── assertion 12: scrub markers present in log (Step 4 + 6) ─────────────"&lt;/span&gt;
assert_log_contains &lt;span class="s2"&gt;"[ADMIN_TOKEN] in log"&lt;/span&gt; &lt;span class="s2"&gt;"[ADMIN_TOKEN]"&lt;/span&gt; &lt;span class="s2"&gt;"4"&lt;/span&gt;
assert_log_contains &lt;span class="s2"&gt;"[ISSUED_KEY] in log"&lt;/span&gt;  &lt;span class="s2"&gt;"[ISSUED_KEY]"&lt;/span&gt;  &lt;span class="s2"&gt;"6"&lt;/span&gt;

&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"── assertion 13: on-call mutation visible in log (Step 7) ───────────────"&lt;/span&gt;
&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;ON_CALL_ENGINEER&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"TEAM"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"   waiting up to 90s for on-call mutation to appear in log..."&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;i &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;seq &lt;/span&gt;1 18&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
    if &lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qF&lt;/span&gt; &lt;span class="s2"&gt;"on-call-engineer"&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$LOG_FILE&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
        &lt;/span&gt;pass &lt;span class="s2"&gt;"on-call mutation in log"&lt;/span&gt;
        &lt;span class="nb"&gt;break
    &lt;/span&gt;&lt;span class="k"&gt;fi
    &lt;/span&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;5
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$i&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-eq&lt;/span&gt; 18 &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;fail &lt;span class="s2"&gt;"on-call mutation in log"&lt;/span&gt; &lt;span class="s2"&gt;"mutation not seen after 90s — check Pollinate:true and mutations goroutine (see step 7)"&lt;/span&gt;
    &lt;span class="k"&gt;fi
done

&lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"════════════════════════════════════════════════════════════════════════"&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"  All assertions passed. Your implementation is correct."&lt;/span&gt;
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"════════════════════════════════════════════════════════════════════════"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make it executable and run it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;chmod&lt;/span&gt; +x test.sh
./test.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If every assertion passes you see the final banner. If any assertion fails the script exits immediately with the step number to revisit. Fix that step and rerun — the script always starts fresh.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 9 — Closing: What You Built and What Comes Next
&lt;/h2&gt;

&lt;p&gt;You started this tutorial with an empty directory and two &lt;code&gt;go get&lt;/code&gt; commands. You finished with a running HTTP server that issues, verifies, and revokes API keys — and whose log file provably contains no plaintext secrets. Run &lt;code&gt;./test.sh&lt;/code&gt; one final time and watch every assertion pass. That exit &lt;code&gt;0&lt;/code&gt; is not a formality. It is a guarantee backed by SHA-512.&lt;/p&gt;

&lt;p&gt;Take a moment to understand what you actually built and why each piece was chosen.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;verbose&lt;/code&gt; is not just a logger. Every other logger in the Go ecosystem — &lt;code&gt;log&lt;/code&gt;, &lt;code&gt;slog&lt;/code&gt;, &lt;code&gt;zap&lt;/code&gt;, &lt;code&gt;zerolog&lt;/code&gt; — will faithfully write whatever string you hand it. &lt;code&gt;verbose&lt;/code&gt; is the only one that knows what your secrets are and refuses to write them. The moment you call &lt;code&gt;verbose.AddSecret(verbose.SecretBytes(token), "[ISSUED_KEY]")&lt;/code&gt;, that token's SHA-512 digest enters a registry and every subsequent log line is scanned against it before it touches disk. The plaintext never persists inside &lt;code&gt;verbose&lt;/code&gt; — not in memory, not on disk. When you called &lt;code&gt;verbose.RemoveSecret&lt;/code&gt; on revocation, you kept that registry lean deliberately: a server that issues thousands of keys over its lifetime should not accumulate thousands of dead secrets in a scanner it runs on every single log call.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;figtree&lt;/code&gt; is not just a flag parser. It is a priority-ordered configuration resolver — file, then environment, then CLI — with validators, rules, and a mutation channel that watches values change at runtime. &lt;code&gt;RuleNoFlags&lt;/code&gt; on &lt;code&gt;kAdminToken&lt;/code&gt; is not decoration. It is an enforcement boundary. The key name &lt;code&gt;"keyserver-admin-token"&lt;/code&gt; being deliberately long is not an accident — it is a design statement: secrets should be inconvenient to pass on a command line because command lines end up in shell history, process lists, and CI logs.&lt;/p&gt;

&lt;p&gt;The on-call rotation is a small thing, but it demonstrates something real: configuration is not static. Values change while a server runs. &lt;code&gt;Pollinate: true&lt;/code&gt; and the mutations channel give you an observable, logged record of every change. In a real system that might be a database password rotating, a feature flag toggling, a rate limit updating. The pattern is identical regardless of what the value is.&lt;/p&gt;




&lt;h3&gt;
  
  
  The packages used in this tutorial are part of a larger body of open source work
&lt;/h3&gt;

&lt;p&gt;Both &lt;code&gt;verbose&lt;/code&gt; and &lt;code&gt;figtree&lt;/code&gt; were written by &lt;strong&gt;Andrei Merlescu&lt;/strong&gt; — &lt;a href="https://github.com/andreimerlescu" rel="noopener noreferrer"&gt;@andreimerlescu&lt;/a&gt; on GitHub. His profile carries 99 public repositories built across 17 years of professional software engineering spanning Cisco, Oracle, Warner Bros. Games, and SurgePays, written primarily in Go, Ruby, PHP, and Bash.&lt;/p&gt;

&lt;p&gt;Some repositories worth exploring alongside the two you just used:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/andreimerlescu/checkfs" rel="noopener noreferrer"&gt;&lt;code&gt;checkfs&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; — filesystem existence and permission checks for &lt;code&gt;File&lt;/code&gt; and &lt;code&gt;Directory&lt;/code&gt;. Pairs naturally with figtree when you want to validate that a config-supplied path actually exists before your server starts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/andreimerlescu/sema" rel="noopener noreferrer"&gt;&lt;code&gt;sema&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; — semaphore primitives for Go. Useful when the keyserver you just built needs to limit concurrent issuance requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/andreimerlescu/go-passwd" rel="noopener noreferrer"&gt;&lt;code&gt;go-passwd&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; — safe password auditing. If your keyserver ever needs to validate that an incoming credential meets a strength policy before issuing a key, this is the package.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/andreimerlescu/summarize" rel="noopener noreferrer"&gt;&lt;code&gt;summarize&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; + &lt;strong&gt;&lt;a href="https://github.com/andreimerlescu/aigcm" rel="noopener noreferrer"&gt;&lt;code&gt;aigcm&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; — AI-assisted project summarization and git commit message generation using local Ollama models. If you are building on an M-series Mac with local models, these are worth looking at.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;&lt;code&gt;lemmings&lt;/code&gt;&lt;/a&gt;&lt;/strong&gt; — load testing built around the concept of NPCs moving through your infrastructure as simulated users across geographic terrains and pack sizes. If you want to know what your keyserver does under real traffic before you ship it, lemmings is how you find out. It was built specifically because existing tools like &lt;code&gt;ab&lt;/code&gt; did not answer the question Andrei needed answered when he launched Trakify in 2015 and got a surge of traffic from a Barron's Magazine feature. The tool he wished he had then exists now and it is free.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;The code is yours. The packages are free. The log file is clean.&lt;/p&gt;

</description>
      <category>logging</category>
      <category>go</category>
      <category>secrets</category>
      <category>security</category>
    </item>
    <item>
      <title>Lemmings Just Spawned</title>
      <dc:creator>Andrei Merlescu</dc:creator>
      <pubDate>Thu, 16 Apr 2026 22:39:15 +0000</pubDate>
      <link>https://dev.to/andreimerlescu/lemmings-just-spawned-3fhe</link>
      <guid>https://dev.to/andreimerlescu/lemmings-just-spawned-3fhe</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc2u9ie5him981tvyr4gi.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fc2u9ie5him981tvyr4gi.jpeg" alt="Lemmings" width="293" height="339"&gt;&lt;/a&gt;&lt;br&gt;
In 1991 a video game called &lt;a href="https://en.wikipedia.org/wiki/Lemmings_(video_game)" rel="noopener noreferrer"&gt;Lemmings&lt;/a&gt; was released and by the time I started Elementary School, I was playing &lt;em&gt;Lemmings&lt;/em&gt;. Fast-forward to 2026, after &lt;a href="https://www.barrons.com/articles/trakify-keeps-you-current-on-your-securities-portfolio-1444457064?gaa_at=eafs&amp;amp;gaa_n=AWEtsqdExXelmOtklurLNX3j9Fqwakc8j8R3xsFFD-qZ7xGd_lXqYro00iG6FoeIXFE%3D&amp;amp;gaa_ts=69d1a438&amp;amp;gaa_sig=TkgzA7hWBhVwj6ggpKRLL8t0tG0Tj6gZBUAlLDqGe6VzU7h4y1Ozis7EkkzIycTrlmSihmhvJ9dgWoTw-ARKQw%3D%3D" rel="noopener noreferrer"&gt;Barron's Magazine&lt;/a&gt; and &lt;a href="https://web.archive.org/web/20160111134734/http://www.trakify.com/" rel="noopener noreferrer"&gt;Trakify&lt;/a&gt; and you have a legitimate need for both the &lt;em&gt;waiting room&lt;/em&gt; of &lt;a href="https://github.com/andreimerlescu/room" rel="noopener noreferrer"&gt;room&lt;/a&gt; and the &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt; themselves. So &lt;em&gt;what is &lt;strong&gt;Lemmings&lt;/strong&gt;?&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introducing Lemmings
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;Lemmings&lt;/a&gt; is a free and open source product that I created after dusting off a notebook from the past that outlined what I thought of when I intersected Software Engineering and the idea of the little critter named Lemming - and what he was doing in his life. When I launched Trakify in 2014, I built clustering and load balancing into my application, but I truly didn't know what I was capable of running - in an authentic trust worthy manner. How do I know that what I am hitting is actually accurate? I felt like tools like &lt;code&gt;ab&lt;/code&gt; simply didn't cut it for what I needed then. After Mike Hogan's piece, I had a massive surge of traffic on the site that resulted in servers being spawned left and right - sooner than later - I realized that I had misconfigured the auto-scaler and needed to take a step back. &lt;/p&gt;

&lt;p&gt;I can only learn from the mistakes of the past and not every company created was going to succeed. Even Trakify had its day and that day came and went, but what didn't leave after Trakify was gone was what I learned from its launch. Essentially, &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt; was the tool I wish I had then, that would have better prepared me for those big publicity moments of my business. When I realized that I needed more collaboration with stronger engineers, I went back into the work force and continued to improve my skills. &lt;/p&gt;

&lt;p&gt;Now, in the rise of AI powered everything - what can I do as a software guy - that is going to improve anything anywhere? Well, that brings me to the &lt;em&gt;why&lt;/em&gt; of &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt;. To me, I spent a handful of hours over the course of a week taking written notes about my &lt;em&gt;lemmings&lt;/em&gt; idea from decades ago and I put it to action in a series of prompts. I leveraged the &lt;a href="https://github.com/andreimerlescu/summarize" rel="noopener noreferrer"&gt;summarize&lt;/a&gt; package in order to deal with cool-down periods between development windows. By the time that I got to &lt;code&gt;v0.0.1&lt;/code&gt;, I'll have a &lt;strong&gt;432 test passing&lt;/strong&gt; piece of code calling &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt; that will let me do what I needed to do when I built &lt;strong&gt;Trakify&lt;/strong&gt; and got recognized by Mr Hogan at Barron's Magazine. &lt;/p&gt;

&lt;h2&gt;
  
  
  What Lemmings Does
&lt;/h2&gt;

&lt;p&gt;What a &lt;em&gt;lemming&lt;/em&gt; &lt;strong&gt;is&lt;/strong&gt; &lt;em&gt;is&lt;/em&gt; a creature that doesn't have much free will or determination in life. It's a fella that gets its marching orders from the video game player's keys telling it to jump or go left or go right. In reality, my lemmings package is similar, in that when performing &lt;strong&gt;load testing&lt;/strong&gt; of any real world application, you'll need NPCs - &lt;strong&gt;N&lt;/strong&gt;on &lt;strong&gt;P&lt;/strong&gt;layer &lt;strong&gt;C&lt;/strong&gt;haracters - that can do a task, and that's it. Lemmings, load balances your endpoint by breaking concurrency into &lt;code&gt;-terrain&lt;/code&gt; and &lt;code&gt;-pack&lt;/code&gt; such that each geographic locality in real life that a &lt;em&gt;lemming&lt;/em&gt; or &lt;em&gt;NPC&lt;/em&gt; or &lt;em&gt;user&lt;/em&gt; have, represents &lt;em&gt;1 terrain&lt;/em&gt;. The number of &lt;em&gt;lemmings&lt;/em&gt; is the &lt;code&gt;-pack&lt;/code&gt; size. &lt;/p&gt;

&lt;p&gt;In 2020 when I built the PhoenixVault that made the NARA released JFK files searchable for the first time since their release in a complete manner - I had a feature called StumbleInto where I took the concept from &lt;em&gt;lemmings&lt;/em&gt; and applied it to the &lt;strong&gt;PhoenixVault&lt;/strong&gt;. Readers, or NPCs or &lt;em&gt;lemmings&lt;/em&gt;, didn't know what they were getting into or where they were going, when they would hit the files and start sleuthing around; but what they did do is keep a log of what pages they saw. Well, that concept was carried forward into &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt;. &lt;/p&gt;

&lt;h2&gt;
  
  
  When Lemmings Win
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;Lemmings&lt;/a&gt; could have better prepared &lt;em&gt;me&lt;/em&gt; for Trakify's launch in 2015. With AI, I was able to bring to life something from the archive's of Andrei's engineering notebooks that solves a real world problem for real world companies. The software is free. If you find use or value in it, support it by spreading the word about it, using it in future projects, and keeping the community up to date with relevant information. &lt;/p&gt;

&lt;p&gt;Where &lt;em&gt;I win&lt;/em&gt; in building and releasing &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt; free and open source is in continuing my life's passion of writing excellent software and serving those who serve others by means of the gifts given to me through decades of hard work by my own two hands. I carry the torch forward and hand forth my legacy in my code by making it open source. If you find value in my work, hire me for my consulting services and I'll apply and use &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt; for your organization to ensure that you don't run into the same challenges that I did in 2015 that &lt;em&gt;lemmings&lt;/em&gt; was &lt;strong&gt;designed&lt;/strong&gt; to remediate through proper knowledge disclosures and testing of key infrastructure. &lt;/p&gt;

&lt;h2&gt;
  
  
  Where Lemmings Will Go
&lt;/h2&gt;

&lt;p&gt;Since I am dusting off old engineering notebooks and compiling complex Markdown files that are draft specifications and instructions for AI systems, the &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt; offering was built over a 72 hour window. When I asked ChatGPT to come up with a project plan for the notebook that I created, it quoted me 4-6 weeks to completion. Nope. Not even 4 days bro. Adding to &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt; is possible, and my notebook for the idea expanded after I built it. Once I had it in hand, I wanted &lt;em&gt;this&lt;/em&gt; and &lt;em&gt;that&lt;/em&gt; and therefore, I began writing once again. Where will &lt;a href="https://github.com/andreimerlescu/lemmings" rel="noopener noreferrer"&gt;lemmings&lt;/a&gt; go? &lt;/p&gt;

</description>
      <category>devops</category>
      <category>testing</category>
      <category>go</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Gin+Go Waiting Room Package Released</title>
      <dc:creator>Andrei Merlescu</dc:creator>
      <pubDate>Tue, 14 Apr 2026 01:25:04 +0000</pubDate>
      <link>https://dev.to/andreimerlescu/gingo-waiting-room-package-released-41me</link>
      <guid>https://dev.to/andreimerlescu/gingo-waiting-room-package-released-41me</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;Drop-in waiting room middleware for &lt;a href="https://github.com/gin-gonic/gin" rel="noopener noreferrer"&gt;gin&lt;/a&gt;&lt;br&gt;
web applications. Built on &lt;a href="https://github.com/andreimerlescu/sema" rel="noopener noreferrer"&gt;sema&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The &lt;a href="https://github.com/andreimerlescu/room" rel="noopener noreferrer"&gt;room&lt;/a&gt; package was created immediately after I updated the &lt;a href="https://github.com/andreimerlescu/sema" rel="noopener noreferrer"&gt;sema&lt;/a&gt; package on GitHub. I wanted to implement the new &lt;a href="https://github.com/andreimerlescu/sema" rel="noopener noreferrer"&gt;sema&lt;/a&gt; package directly by consuming it a real world manner that could bring tangible value to other developers, thus the &lt;a href="https://github.com/andreimerlescu/room" rel="noopener noreferrer"&gt;room&lt;/a&gt; package was created.&lt;/p&gt;

&lt;p&gt;I've been a &lt;a href="https://github.com/andreimerlescu" rel="noopener noreferrer"&gt;professional Go developer&lt;/a&gt; for 6 years now and have enterprise production software running in over 3 Fortune® 100 Companies™. Not only is Go an incredible capable and powerful programming language, it is naturally very easy to engage with AI systems in order to build utilities like &lt;em&gt;room&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Quite literally yesterday I finished up with &lt;em&gt;sema&lt;/em&gt; and I immediately began consuming it by building out &lt;strong&gt;room&lt;/strong&gt;. The first room that I could come up with was the &lt;em&gt;Waiting Room&lt;/em&gt; which is the concept of rate limiting with a presentation layer. Except, I wanted to take the presentation layer to the next level, and so I introduced the concept of promotions within a FIFO - &lt;em&gt;&lt;strong&gt;F&lt;/strong&gt;irst &lt;strong&gt;I&lt;/strong&gt;n &lt;strong&gt;F&lt;/strong&gt;irst &lt;strong&gt;O&lt;/strong&gt;ut&lt;/em&gt; queue that cleverly keeps track of fast tracking for up to a defined amount. The demo has it at 90 minutes; but an application under extreme load - where a premium user may end up getting show the waiting room multiple times - gets the best of the promote world in that they jump to the front of the line for the entire duration of their promotional window. The &lt;a href="https://github.com/andreimerlescu/room/blob/main/sample/basic-web-app/test.sh" rel="noopener noreferrer"&gt;test.sh&lt;/a&gt; in the &lt;strong&gt;sample/basic-web-app&lt;/strong&gt; consumer of the &lt;code&gt;room&lt;/code&gt; demonstrates this functionality in less than 30 seconds. &lt;/p&gt;

&lt;p&gt;Sometimes high traffic events come into an application and your business cannot justify spending tens of thousands of dollars on infrastructure that may or may not be serving paying customers. If you're launching a CD and you allow pre-orders on your website at 9AM and you have a flood of traffic coming into the site, the ability to buy your way to the front of the line gives you the capability to give yourself preference in commercial settings and get to the front of the line. Even if the website isn't selling something explicit, what the &lt;code&gt;room&lt;/code&gt; allows you to implement is something like this: &lt;em&gt;this $5 pass is good for 90 minutes and during our launch you'll breeze through every page in our service regardless of capacity limitations that we experience&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Why is this valuable? Not everything on the internet is operating with an infinite budget to spend on cloud hosting. That's where my software comes into play. I'm a 21+ year DevOps architect that has delivered dozens of products internally to Big Tech my entire career and released over 100+ open source packages, including dozens in Go. When you can one-shot a Go website using Gin and Claude, you may not want to pay for auto-scaling bot traffic that costs you $50,000 per month. Instead, you can put the waiting room on, and then voila, you can run for $100/mo and then give customers who buy the pass a store credit for their checkout.&lt;/p&gt;

&lt;p&gt;It's a way to give priority to the human over the bot in the event that the bot isn't providing some kind of commercial benefit to the website they are connecting to in the first place. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;room&lt;/code&gt; package implements &lt;code&gt;sema&lt;/code&gt; in a way that demonstrates how &lt;em&gt;that&lt;/em&gt; package can be implemented in a real world scenario. The &lt;code&gt;room&lt;/code&gt; package can be implemented in real world scenarios as well, including &lt;em&gt;your website&lt;/em&gt; that uses Gin+Go to serve content. &lt;/p&gt;

&lt;p&gt;The package is Apache 2.0 and is released Open Source on &lt;a href="https://github.com/andreimerlescu/room" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;. &lt;/p&gt;

</description>
      <category>devops</category>
      <category>go</category>
      <category>security</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
