<?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: Johnny Santamaria</title>
    <description>The latest articles on DEV Community by Johnny Santamaria (@jds64).</description>
    <link>https://dev.to/jds64</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2196487%2Fcc8072cd-314c-42fe-a09e-3a1422132a2e.png</url>
      <title>DEV Community: Johnny Santamaria</title>
      <link>https://dev.to/jds64</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jds64"/>
    <language>en</language>
    <item>
      <title>MudMap: A Voice-First Trail Logger That Works Without a Signal</title>
      <dc:creator>Johnny Santamaria</dc:creator>
      <pubDate>Sun, 11 Oct 2026 19:47:27 +0000</pubDate>
      <link>https://dev.to/jds64/mudmap-a-voice-first-trail-logger-that-works-without-a-signal-4mng</link>
      <guid>https://dev.to/jds64/mudmap-a-voice-first-trail-logger-that-works-without-a-signal-4mng</guid>
      <description>&lt;h2&gt;
  
  
  MudMap: A Voice-First Trail Logger That Works Without a Signal
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-week1-2026-10-05"&gt;Hacktoberfest Open-Source AI Challenge Week 1: Touch Grass&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;MudMap is an offline voice trail logger. You press Record, talk while walking — &lt;em&gt;"Saw a heron, trail washed out past the creek, blackberries ripe"&lt;/em&gt; — and the app transcribes it on-device, extracts species and conditions, and drops a pin on a map you own. No cloud. No account. No signal required.&lt;/p&gt;

&lt;p&gt;It exists to make the screen the shortest part of a hike. You don't take out your phone to type notes. You don't open an app to browse a feed. You walk, you talk, you put the phone away. Everything else happens after.&lt;/p&gt;

&lt;p&gt;The typical user is someone who already walks outdoors and wants a record of what they saw — birders, foragers, trail runners, gardeners — without a subscription app that uploads their favorite fishing hole to a server they don't control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;The build produces a working debug APK. It builds cleanly in CI, unit tests pass, and the artifact installs on any modern Android phone (arm64-v8a).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current state, honestly stated:&lt;/strong&gt; the software pipeline is proven, but hardware verification on real trails is still pending. That's the next step, and it's why I'm looking for Android testers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Known limit to flag upfront:&lt;/strong&gt; the bundled basemap is a San Francisco demo extract only (longitude -122.53 to -122.35, latitude 37.70 to 37.83). Outside SF, pins appear on a blank basemap — your entries still save, you just won't see a basemap under them until you prepare a replacement region.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The repository is at &lt;strong&gt;&lt;a href="https://github.com/johnny603/MudMap" rel="noopener noreferrer"&gt;https://github.com/johnny603/MudMap&lt;/a&gt;&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A build is produced on every push to &lt;code&gt;main&lt;/code&gt; via GitHub Actions, and tagged releases produce a permanent download URL.&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;MudMap is built on four open-source pieces, each load-bearing:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Open-source component&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Speech-to-text&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;whisper.cpp&lt;/strong&gt; v1.9.5&lt;/td&gt;
&lt;td&gt;Transcribes the &lt;code&gt;.m4a&lt;/code&gt; recording on-device&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Map rendering&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;MapLibre Native&lt;/strong&gt; 12.3.1&lt;/td&gt;
&lt;td&gt;Renders the map and pins&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Basemap&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Protomaps&lt;/strong&gt; (PMTiles)&lt;/td&gt;
&lt;td&gt;Offline vector tiles bundled in the APK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Storage&lt;/td&gt;
&lt;td&gt;
&lt;strong&gt;Room&lt;/strong&gt; / SQLite&lt;/td&gt;
&lt;td&gt;Persists entries locally&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The stack is Kotlin + Android Views, minSdk 26, targeting arm64-v8a. The native whisper layer is compiled against the Android NDK 27 and invoked through a small JNI adapter.&lt;/p&gt;

&lt;p&gt;The architecture is deliberately small. There's a &lt;code&gt;Transcriber&lt;/code&gt; port and a &lt;code&gt;WhisperCppAdapter&lt;/code&gt; behind it; an &lt;code&gt;EntityExtractor&lt;/code&gt; port with a &lt;code&gt;RuleBasedExtractor&lt;/code&gt; behind it. That means the AI pieces are swappable — if a better on-device model drops tomorrow, I change one adapter, not the app.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Walkthrough of one use case:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;User presses &lt;strong&gt;Record&lt;/strong&gt;. The app requests one location fix (10-second timeout) and starts capturing &lt;code&gt;.m4a&lt;/code&gt; audio to app-private storage.&lt;/li&gt;
&lt;li&gt;User presses &lt;strong&gt;Stop&lt;/strong&gt;. The location fix is frozen — if none arrived, the entry saves with null coordinates.&lt;/li&gt;
&lt;li&gt;The AAC audio is decoded to 16 kHz mono float PCM — the format whisper.cpp expects.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;whisper.cpp&lt;/strong&gt; transcribes the PCM on-device. No network call.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;RuleBasedExtractor&lt;/code&gt; matches the transcript against a bundled vocabulary and produces tags: species, conditions, landmarks.&lt;/li&gt;
&lt;li&gt;The six-field entry is saved to &lt;strong&gt;Room&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;The map reads entries on launch and after saves. A pin opens a transcript-and-tags bottom sheet.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every step except &lt;em&gt;installing the app&lt;/em&gt; happens with the phone in airplane mode.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;The build itself turned into the hardest part of this project — harder than any of the app logic. Three problems in particular cost real time, and each needed several attempts before it broke.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 1: The cryptic &lt;code&gt;25.0.4.1&lt;/code&gt; build failure
&lt;/h3&gt;

&lt;p&gt;The first Gradle run failed with a message that said nothing useful:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;FAILURE: Build failed with an exception.

* What went wrong:
25.0.4.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No file, no line, no explanation. Just a version number.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attempt 1 — Re-ran the build.&lt;/strong&gt; Same failure, same 46-second wait.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attempt 2 — Searched for the error string.&lt;/strong&gt; &lt;code&gt;25.0.4.1&lt;/code&gt; returns nothing useful; it's not a documented Gradle or AGP error code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attempt 3 — Read the README's pinned versions table carefully.&lt;/strong&gt; It specified &lt;strong&gt;JDK 17&lt;/strong&gt;. My shell was on a newer JDK, and the "Restricted method" warnings in the build output were the tell. Gradle 8.11.1 and AGP 8.9.2 do not support JDKs past 17 in this configuration — and instead of saying so, they emit a bare version string.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resolution:&lt;/strong&gt; forced &lt;code&gt;JAVA_HOME&lt;/code&gt; to JDK 17 for the build shell:&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;export &lt;/span&gt;&lt;span class="nv"&gt;JAVA_HOME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;/usr/libexec/java_home &lt;span class="nt"&gt;-v&lt;/span&gt; 17&lt;span class="si"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The build then proceeded to the next failure. I added this diagnosis to the README under &lt;em&gt;Common build failures&lt;/em&gt; so the next person doesn't lose the same hour.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 2: The asset script couldn't find &lt;code&gt;pmtiles&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;bash scripts/prepare-assets.sh&lt;/code&gt; failed with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Install the pmtiles CLI, or set PMTILES to its absolute path.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Attempt 1 — Installed &lt;code&gt;pmtiles&lt;/code&gt; via Homebrew.&lt;/strong&gt; It installed, but the script still failed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attempt 2 — Verified the binary.&lt;/strong&gt; Ran &lt;code&gt;pmtiles --version&lt;/code&gt; — which returned &lt;code&gt;unknown flag --version&lt;/code&gt;. That's not a bug; the CLI uses the &lt;code&gt;version&lt;/code&gt; subcommand. But it made it unclear whether the install had worked at all. &lt;code&gt;which pmtiles&lt;/code&gt; and &lt;code&gt;pmtiles --help&lt;/code&gt; confirmed it was on &lt;code&gt;PATH&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attempt 3 — Passed the path explicitly.&lt;/strong&gt; The README documents an escape hatch, &lt;code&gt;PMTILES=/absolute/path/to/pmtiles&lt;/code&gt;, which bypasses the &lt;code&gt;PATH&lt;/code&gt; lookup entirely. That worked, and the asset pipeline completed: it downloaded a 4.1 MB extract, verified it in 308µs, and printed &lt;code&gt;Offline assets ready.&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;The lesson: a tool being "installed" and a script finding it are different problems. The explicit-path override is the reliable fix.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 3: The disk-space wall
&lt;/h3&gt;

&lt;p&gt;With JDK and &lt;code&gt;pmtiles&lt;/code&gt; resolved, the Android SDK install failed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Warning: An error occurred while preparing SDK package NDK (Side by side)
27.0.12077973: No space left on device.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The NDK is roughly 3–4 GB. The disk had &lt;strong&gt;662 MB free&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attempt 1 — Homebrew cleanup.&lt;/strong&gt; &lt;code&gt;brew cleanup --prune=all&lt;/code&gt; reclaimed ~1.1 GB. Good, but nowhere near enough — the full Android toolchain plus Gradle plus the whisper.cpp native compile needs roughly &lt;strong&gt;25–30 GB&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attempt 2 — Cleared Gradle caches and the Homebrew download cache.&lt;/strong&gt; Another gigabyte or so. Still an order of magnitude short.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Attempt 3 — Moved the build off the machine entirely.&lt;/strong&gt; Instead of fighting for 30 GB on a MacBook Air that already had 198 GB used, I wrote a GitHub Actions workflow. The runner has its own disk. The build succeeded in &lt;strong&gt;3 minutes 22 seconds&lt;/strong&gt;, producing a 147 MB debug APK.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resolution:&lt;/strong&gt; CI builds the APK; the local machine never needs the Android toolchain. This also became the distribution mechanism — tagged releases now produce a permanent download URL from the same workflow.&lt;/p&gt;

&lt;h3&gt;
  
  
  What these three have in common
&lt;/h3&gt;

&lt;p&gt;None of them were problems with MudMap's code. All three were environment problems, and all three had error messages that pointed away from the actual cause. That's the ordinary tax of getting an offline native app to build on a machine that wasn't set up for it — and it's why the README now has a &lt;em&gt;Prerequisites&lt;/em&gt; section and a &lt;em&gt;Common build failures&lt;/em&gt; table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;p&gt;The open pieces aren't a nice-to-have here — they're the reason the thing works where it's meant to be used.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It works with no signal.&lt;/strong&gt; Whisper.cpp runs on the phone. There's no server round-trip for transcription. A closed speech API would need a cell tower, and the trail has none.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your data stays on your phone.&lt;/strong&gt; Internet permission is removed entirely from the manifest. Cloud backup and device transfer are excluded. Your trail notes are yours. A closed equivalent would store your location history on someone else's disk, where it becomes part of a data set you don't own.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The map is a file, not a subscription.&lt;/strong&gt; Protomaps ships offline tiles as a &lt;code&gt;.pmtiles&lt;/code&gt; archive bundled in the APK. MapLibre reads it locally. No tile-server bill, no API key, no per-request quota. A closed map SDK would meter every pan and zoom.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You can swap the model.&lt;/strong&gt; The &lt;code&gt;Transcriber&lt;/code&gt; and &lt;code&gt;EntityExtractor&lt;/code&gt; ports mean the AI backend is replaceable. If a smaller, faster, better model drops — or if you want to fine-tune one on your own observations — you change one adapter. No rewrite.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It costs nothing to run.&lt;/strong&gt; There is no server component. There is no monthly bill. The APK does everything.&lt;/p&gt;

&lt;p&gt;The "open" part is what makes the whole thing possible &lt;em&gt;offline, on a trail, away from infrastructure&lt;/em&gt;. That's the point of the project.&lt;/p&gt;

&lt;p&gt;Hacktoberfest 2026 is a month-long celebration of open source, and this year it's specifically about building with open-weight models and open-source AI — which is exactly what MudMap is. The event runs all October, online and through 300+ in-person Fests, and anyone can take part regardless of where they are in their open-source journey. For MudMap, my immediate plan is to cut a v1.0.0 release with a permanent download link, then recruit Android testers to verify the software on real trails — microphone capture, GPS acquisition, transcription latency, and battery use. After that, the next slice of work is the deferred list: sync, delete, undo, and LLM-based entity extraction via Ollama. What I've learned building this is that the open pieces aren't decoration — they're the reason the app works where it's meant to be used, and Hacktoberfest is the right place to share that with people who care about the same thing.&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>hf26challenge</category>
      <category>ai</category>
    </item>
    <item>
      <title>CscGrader and generateRubrics: Open-Source Tools for Faster CS Grading</title>
      <dc:creator>Johnny Santamaria</dc:creator>
      <pubDate>Sun, 04 Oct 2026 23:26:53 +0000</pubDate>
      <link>https://dev.to/jds64/cscgrader-and-generaterubrics-open-source-tools-for-faster-cs-grading-3n9k</link>
      <guid>https://dev.to/jds64/cscgrader-and-generaterubrics-open-source-tools-for-faster-cs-grading-3n9k</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/hacktoberfest-weekend-2026-10-01"&gt;Hacktoberfest Weekend Challenge: Build for a Friend&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built a two-part toolkit to solve the biggest time-sink in teaching large CS courses: grading massive, complex student projects. This isn't about auto-grading, which often fails for creative or multi-part assignments. Instead, it’s about automating the tedious, mechanical work so instructors can focus on providing meaningful feedback.&lt;/p&gt;

&lt;p&gt;I built this for my friend, a CS professor who was spending entire weekends manually compiling, running, and debugging hundreds of student submissions for a single assignment.&lt;/p&gt;

&lt;p&gt;The problem is simple: when you have 150 students submitting multi-file Java projects, just getting each one to compile and run with the correct test input is a logistical nightmare. It’s slow, error-prone, and mentally draining.&lt;/p&gt;

&lt;p&gt;My solution is a two-part open-source toolkit:&lt;/p&gt;

&lt;p&gt;CscGrader: A language-agnostic tool that automates the "collection of evidence" phase. It detects student projects, compiles them, runs them with predefined inputs, and captures all the output (stdout, stderr, exit codes, timing).&lt;/p&gt;

&lt;p&gt;generateRubrics: A simple utility that takes a single master rubric file (Excel, PDF, etc.) and generates a personalized, ready-to-grade copy for every student on the roster.&lt;/p&gt;

&lt;p&gt;Together, these tools transform a weekend of grunt work into a streamlined, semi-automated workflow, freeing up my friend to do what they do best: teaching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Demo
&lt;/h2&gt;

&lt;p&gt;The tools are designed for a command-line workflow. Here’s how they would be used together for a single assignment:&lt;/p&gt;

&lt;p&gt;Step 1: Generate all the grading rubrics for the class.&lt;/p&gt;

&lt;p&gt;bash&lt;/p&gt;

&lt;h1&gt;
  
  
  studentNames.txt contains a list of all students in the class
&lt;/h1&gt;

&lt;p&gt;python3 generateRubrics.py -inputFile studentNames.txt -fileToCopy "Assignment-03-Rubric.xlsx" --assignment 03&lt;br&gt;
This instantly creates a Rubrics/ folder with perfectly named files like JaneDoe-Assignment-03-Rubric.xlsx.&lt;/p&gt;

&lt;p&gt;Step 2: Process all student submissions to collect evidence.&lt;/p&gt;

&lt;p&gt;bash&lt;/p&gt;

&lt;h1&gt;
  
  
  The submissions folder contains one subfolder per student
&lt;/h1&gt;

&lt;h1&gt;
  
  
  The assignment profile tells CscGrader what to look for and how to run it
&lt;/h1&gt;

&lt;p&gt;cscgrader batch ./submissions/ --assignment ./csc215-assignment-03.json --input test-input.txt&lt;br&gt;
This processes every submission, compiles the Java code, runs it with the same test input, and saves a structured JSON file with all the results for each student.&lt;/p&gt;

&lt;p&gt;Step 3: Grade.&lt;/p&gt;

&lt;p&gt;My friend now opens the personalized rubric for a student in one window and the corresponding JSON evidence file in another. All the "did it compile? did it run? what was the output?" questions are already answered. They can now focus on code quality, design, and feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;Here are the two repositories:&lt;/p&gt;

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

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

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;This project is a prime example of using open-source tools for practical, targeted automation.&lt;/p&gt;

&lt;p&gt;Core Logic: Both tools are written in Python, relying only on the standard library. This was a deliberate choice to make them as portable and easy to run as possible for instructors who may not have admin rights or want to deal with virtual environments. The generateRubrics tool treats files as binary data, so it can copy .xlsx rubrics without needing libraries like openpyxl.&lt;/p&gt;

&lt;p&gt;AI-Assisted Development: I used an open-weight model as a coding partner throughout the process. It was instrumental in designing the modular architecture of CscGrader, specifically the idea of using "assignment profiles" (JSON files) to make the tool reusable across different courses and languages.&lt;/p&gt;

&lt;p&gt;Agent Harness: I used an open-source agent harness to automate the testing of CscGrader. The agent would create dummy Java projects with common errors (e.g., syntax errors, missing dependencies, ClassNotFoundException), run CscGrader on them, and verify that the tool correctly identified the failure category. This saved me hours of manual testing and made the tool far more robust.&lt;/p&gt;

&lt;p&gt;The architecture of CscGrader is built around a modular pipeline: Detection → Compilation → Execution → Evidence. This is all orchestrated by a central runner, which uses language-specific "adapters" to handle the unique aspects of each programming language. The agent harness was key to building and validating this complex workflow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why Does Open Innovation Matter?
&lt;/h2&gt;

&lt;h2&gt;
  
  
  My Agent Session
&lt;/h2&gt;

&lt;h2&gt;
  
  
  Prize Categories
&lt;/h2&gt;

</description>
      <category>devchallenge</category>
      <category>weekendchallenge</category>
      <category>hf26challenge</category>
    </item>
    <item>
      <title>Using AI as a Learning Tool: Lessons From the Classroom, STEM, and Software Engineering</title>
      <dc:creator>Johnny Santamaria</dc:creator>
      <pubDate>Thu, 01 Oct 2026 04:01:57 +0000</pubDate>
      <link>https://dev.to/jds64/using-ai-as-a-learning-tool-lessons-from-the-classroom-stem-and-software-engineering-4jgf</link>
      <guid>https://dev.to/jds64/using-ai-as-a-learning-tool-lessons-from-the-classroom-stem-and-software-engineering-4jgf</guid>
      <description>&lt;p&gt;"Hello, I just wanted to check in..."&lt;/p&gt;

&lt;p&gt;I did not start thinking seriously about AI from one role. I started seeing it differently as I moved between being a computer science student, teaching STEM, working as an LA/DL, doing educational research, and working at a robotics startup. Each experience showed me a different part of the same problem: &lt;strong&gt;how can we use technology to make people better at learning and solving problems without taking the problem-solving away from them?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As an Instructional Student Assistant at San Francisco State University, I work with students on programming logic and software engineering while developing Python automation for academic workflows. As an E-GAISE research assistant, I have analyzed submissions from 34 student teams and contributed to research involving AI-integrated grading systems. Before that, I worked as an Engineering Operator at a robotics startup, where I built Python tools for data ingestion, labeling, and validation.&lt;/p&gt;

&lt;p&gt;These experiences led me to my main thesis: &lt;strong&gt;AI is most valuable in STEM education when it provides context-aware assistance, creates opportunities for practice and feedback, and keeps humans responsible for reasoning and verification.&lt;/strong&gt; A student can create a diagram before asking AI for design feedback. An instructor can provide course objectives and common mistakes before using AI to create a Kahoot, Blooket, or debugging game. A developer can provide an existing codebase before asking AI to make a change.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI Needs Context
&lt;/h2&gt;

&lt;p&gt;One of the most useful ways I see AI being used in a classroom is by giving it context rather than starting with an empty prompt.&lt;/p&gt;

&lt;p&gt;For example, imagine that a student is working on a software design assignment. Instead of immediately asking an AI, "How should I build this?", the student could first create a class diagram, flowchart, wireframe, pseudocode outline, or system design.&lt;/p&gt;

&lt;p&gt;The student could then give that design to the AI and ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Does this design satisfy the requirements?&lt;/li&gt;
&lt;li&gt;What relationships am I missing?&lt;/li&gt;
&lt;li&gt;Is there unnecessary complexity?&lt;/li&gt;
&lt;li&gt;What edge cases should I consider?&lt;/li&gt;
&lt;li&gt;What could I improve?&lt;/li&gt;
&lt;li&gt;What tradeoffs am I making?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates a different learning experience.&lt;/p&gt;

&lt;p&gt;The AI now has context about what the student is thinking, and the student has already performed part of the intellectual work. The AI becomes something that can critique and question the student's thinking instead of generating the entire solution.&lt;/p&gt;

&lt;p&gt;I think this is especially important in computer science because design is part of the learning process. If a student creates a diagram, writes pseudocode, or explains an algorithm before using AI, there is something concrete to evaluate.&lt;/p&gt;

&lt;p&gt;The same principle applies to professional software development. A developer can provide an existing codebase, architecture diagram, API specification, test case, or error log before asking AI for help. The more useful question is not simply, "Can AI write this?" but "What context does AI need to understand what I am trying to accomplish?"&lt;/p&gt;

&lt;h2&gt;
  
  
  AI and Gamified Learning
&lt;/h2&gt;

&lt;p&gt;Another classroom application I have become interested in is using AI to create short, bite-sized learning activities.&lt;/p&gt;

&lt;p&gt;During my LA/DL sessions, I use activities similar to Kahoot and Blooket to review programming concepts and assignments. I have found that these activities can make a 50-minute session more interactive because students are not simply listening to a lecture.&lt;/p&gt;

&lt;p&gt;AI could help instructors create variations of these activities.&lt;/p&gt;

&lt;p&gt;For example, I could provide AI with the assignment objectives, course concepts, common student mistakes, and examples of the code being taught. AI could then help generate questions for a Kahoot, Blooket, or another classroom game.&lt;/p&gt;

&lt;p&gt;The instructor would still need to review the material before using it, but AI could make it much faster to create several versions of an activity.&lt;/p&gt;

&lt;p&gt;The activity could also go beyond a traditional multiple-choice quiz. A programming class could have a &lt;strong&gt;Code Detective&lt;/strong&gt; game where students receive clues about a programming bug and identify the problematic line. Another activity could ask students to predict what a program will output before running it. Students could work in teams to determine which algorithm would be most appropriate for a particular problem.&lt;/p&gt;

&lt;p&gt;The goal is not to use AI simply because it is available. The goal is to use it to create more opportunities for students to practice.&lt;/p&gt;

&lt;p&gt;This connects to my previous experience teaching STEM, where I used project-based activities involving coding, game design, and robotics. I learned that students can understand technical concepts differently depending on how the material is presented. A short game, demonstration, or hands-on activity can sometimes create a better entry point than another explanation.&lt;/p&gt;

&lt;p&gt;AI gives educators another way to experiment with those activities.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI as a Learning Assistant
&lt;/h2&gt;

&lt;p&gt;AI can also be useful when I am the student.&lt;/p&gt;

&lt;p&gt;When I encounter a difficult programming problem, I do not necessarily want an AI system to give me the finished solution. Doing that can remove one of the most important parts of computer science education: learning how to reason through a problem.&lt;/p&gt;

&lt;p&gt;Instead, I can ask questions such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What concept am I misunderstanding?&lt;/li&gt;
&lt;li&gt;What edge case am I missing?&lt;/li&gt;
&lt;li&gt;Can you give me a hint without giving me the solution?&lt;/li&gt;
&lt;li&gt;Why does my approach fail?&lt;/li&gt;
&lt;li&gt;What should I test next?&lt;/li&gt;
&lt;li&gt;Can you explain this error differently?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I can also give AI my own work first.&lt;/p&gt;

&lt;p&gt;For example, I might write pseudocode, create a class diagram, implement a first version, or explain my reasoning before asking for feedback. This gives the AI something concrete to evaluate.&lt;/p&gt;

&lt;p&gt;It also forces me to confront my own understanding before asking for help.&lt;/p&gt;

&lt;p&gt;That is an important distinction between &lt;strong&gt;using AI to learn&lt;/strong&gt; and &lt;strong&gt;using AI to avoid learning&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What My LA/DL Experience Taught Me
&lt;/h2&gt;

&lt;p&gt;My experience as an LA/DL has made this distinction even clearer.&lt;/p&gt;

&lt;p&gt;A student does not always need more information. Sometimes they need the information presented differently. Sometimes they need someone to ask a question that helps them identify their own mistake.&lt;/p&gt;

&lt;p&gt;For example, if a student is confused about tracing code, I might change the activity and have them diagram the execution of the program. If several students make the same mistake, I can turn that mistake into a group debugging activity.&lt;/p&gt;

&lt;p&gt;I have also learned that engagement matters. A 50-minute session is relatively short, so I try to structure the time around an icebreaker, a game or review activity, assignment content, demonstrations, and a workshop where students can ask questions and begin working.&lt;/p&gt;

&lt;p&gt;AI could help me create more variations of these activities, but it does not know my students in the same way that I do.&lt;/p&gt;

&lt;p&gt;I still have to decide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What should students learn?&lt;/li&gt;
&lt;li&gt;What is appropriate for their current level?&lt;/li&gt;
&lt;li&gt;What misconceptions are they experiencing?&lt;/li&gt;
&lt;li&gt;How much content can realistically fit into 50 minutes?&lt;/li&gt;
&lt;li&gt;Which activity will encourage participation?&lt;/li&gt;
&lt;li&gt;Is the material appropriate for this particular class?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where the human role remains important. AI can assist with preparation, but the instructor still has to understand the students and make the educational decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI in Educational Research
&lt;/h2&gt;

&lt;p&gt;My research experience has given me another perspective on AI in education.&lt;/p&gt;

&lt;p&gt;At San Francisco State University, I analyzed submissions from 34 student teams across two semesters using Python and Pandas. The work involved extracting chat logs and reflective writing and analyzing performance across multiple rubric dimensions. I also contributed to research involving AI-integrated grading systems and educational technology.&lt;/p&gt;

&lt;p&gt;I have also worked on an E-GAISE agentic grading pipeline that uses Python, LangGraph, Pandas, and Pydantic to evaluate submissions against a rubric. The system uses evidence and validation mechanisms rather than simply accepting an AI-generated evaluation as correct.&lt;/p&gt;

&lt;p&gt;This experience has made me more interested in &lt;strong&gt;how AI is integrated into a workflow&lt;/strong&gt;, rather than simply whether AI is being used.&lt;/p&gt;

&lt;p&gt;For example, an AI-assisted grading system needs a clear rubric, evidence from the student's work, and a way to evaluate whether its conclusions are consistent. The important question is not whether AI can produce a grade, but whether the overall process produces useful and defensible feedback.&lt;/p&gt;

&lt;p&gt;That perspective connects closely to my experience in software engineering.&lt;/p&gt;

&lt;h2&gt;
  
  
  Human-in-the-Loop: What I Learned From Robotics
&lt;/h2&gt;

&lt;p&gt;My experience at a robotics startup helped me understand why this distinction matters.&lt;/p&gt;

&lt;p&gt;My work involved data ingestion, labeling, validation, and internal tooling. The technical problem was not simply getting software to run. The larger problem was making sure the data moving through the workflow could actually be trusted.&lt;/p&gt;

&lt;p&gt;I learned that automation is valuable because it can reduce repetitive work and make systems faster. But automation can also make mistakes faster.&lt;/p&gt;

&lt;p&gt;That lesson carries directly into AI.&lt;/p&gt;

&lt;p&gt;An AI system can generate ten explanations in seconds, but if those explanations are incorrect, it has simply produced ten incorrect explanations faster. The solution is not necessarily to avoid automation. Instead, the workflow needs enough context, testing, and human judgment to determine whether the result is useful.&lt;/p&gt;

&lt;p&gt;This gives me a model for how I want to use AI:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Context → AI assistance → Human review → Feedback → Iteration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The same workflow can apply to a classroom activity, an AI-generated explanation, an automated grading system, or a software development task.&lt;/p&gt;

&lt;p&gt;The human does not have to perform every step manually. Instead, AI can handle parts of the process while the human remains responsible for understanding the goal and deciding whether the result is acceptable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Risk of Overusing AI
&lt;/h2&gt;

&lt;p&gt;The biggest concern I have with AI in education is that convenience can become a substitute for learning.&lt;/p&gt;

&lt;p&gt;If a student asks an AI to solve every programming assignment, they may complete the assignment without developing the ability to solve a similar problem independently.&lt;/p&gt;

&lt;p&gt;There is a major difference between:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Give me the answer."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;and:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Here is my design. Can you help me find weaknesses in it?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The second approach preserves the student's role in the learning process.&lt;/p&gt;

&lt;p&gt;The student has made decisions, created an artifact, and thought about the problem. AI becomes a source of feedback instead of a replacement for reasoning.&lt;/p&gt;

&lt;p&gt;This is why I think educators should focus less on simply asking whether students are using AI and more on teaching students how to use it responsibly.&lt;/p&gt;

&lt;p&gt;Students should know how to question AI output, test generated code, verify information, recognize incorrect assumptions, and explain their own work.&lt;/p&gt;

&lt;h2&gt;
  
  
  What AI Has Taught Me About Learning
&lt;/h2&gt;

&lt;p&gt;My experiences have changed my definition of effective AI use.&lt;/p&gt;

&lt;p&gt;I used to think about AI primarily as something that could make tasks faster. Now I think about it more as something that can create additional opportunities for experimentation.&lt;/p&gt;

&lt;p&gt;As a student, I can use AI to challenge my reasoning.&lt;/p&gt;

&lt;p&gt;As an educator, I can use it to brainstorm activities, create practice problems, and develop different explanations.&lt;/p&gt;

&lt;p&gt;As a researcher, I can investigate how AI changes educational workflows and how those systems can be evaluated.&lt;/p&gt;

&lt;p&gt;As a software developer, I can use AI to investigate problems while still being responsible for the resulting system.&lt;/p&gt;

&lt;p&gt;In all of these situations, the most valuable workflow is not:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Human → AI → Answer&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It is closer to:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Human → Context/Design → AI → Feedback → Human Decision → Iteration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That distinction is important.&lt;/p&gt;

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

&lt;p&gt;AI will continue to change STEM education and software engineering, but I do not think the future should be about replacing the human parts of learning.&lt;/p&gt;

&lt;p&gt;My experiences in education and software engineering have taught me that context, iteration, feedback, and human judgment matter.&lt;/p&gt;

&lt;p&gt;In the classroom, that could mean creating a design before asking AI for feedback. It could mean using AI to turn common programming mistakes into a short debugging game. It could mean generating a Kahoot or Blooket from course material and then adapting it to the needs of the class.&lt;/p&gt;

&lt;p&gt;For a student, it could mean creating pseudocode before asking AI for implementation feedback. For a developer, it could mean giving AI enough information about an existing system before asking it to make a change. For an educational researcher, it could mean designing an AI-assisted grading workflow around evidence and clear evaluation criteria.&lt;/p&gt;

&lt;p&gt;These approaches have something in common: &lt;strong&gt;AI is helping with the process, but humans remain responsible for the thinking.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is the lesson I am carrying forward from my work as a student, educator, researcher, and engineer.&lt;/p&gt;

&lt;p&gt;AI should not make us think less.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It should give us better ways to think, practice, test, create, and learn.&lt;/strong&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>programming</category>
      <category>career</category>
    </item>
    <item>
      <title>Lessons from a Robotics Startup: What I Learned About Data Pipelines</title>
      <dc:creator>Johnny Santamaria</dc:creator>
      <pubDate>Sun, 09 Aug 2026 00:01:46 +0000</pubDate>
      <link>https://dev.to/jds64/lessons-from-a-robotics-startup-what-i-learned-about-data-pipelines-138j</link>
      <guid>https://dev.to/jds64/lessons-from-a-robotics-startup-what-i-learned-about-data-pipelines-138j</guid>
      <description>&lt;p&gt;"Smile because it happened" — Dr. Seuss&lt;/p&gt;

&lt;h2&gt;
  
  
  The Setup
&lt;/h2&gt;

&lt;p&gt;Earlier this year, I took on a short-term trial role with an early-stage robotics startup. The premise was straightforward: help with data collection, annotation, and evaluation workflows—essentially the backbone of any modern robotics or embodied-AI system.&lt;br&gt;
The trial didn't work out long-term. I was let go after about two months — a decision that, honestly, came down in part to my bandwidth as a student. Balancing a full course load with a startup trial was harder than I anticipated.&lt;br&gt;
But that's not the story I want to tell. What I do want to share are the technical lessons I took away — lessons about building robust data pipelines, about the gap between theory and practice, and about what I'd do differently next time.&lt;/p&gt;

&lt;p&gt;These aren't company secrets. They're about the general engineering challenges that anyone working with robotics data pipelines will encounter — challenges I'd read about in papers but hadn't truly internalized until I was standing in front of them.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Data Pipeline Shape Is Universal—But the Details Aren't
&lt;/h2&gt;

&lt;p&gt;If you've spent any time in ML or robotics, you've seen this described:&lt;br&gt;
Data Collection → Annotation → Evaluation&lt;br&gt;
It's a standard three-stage pipeline. Industry vendors describe it explicitly in their robotics content. Academic projects model this structure. It's the field's shared vocabulary. Companies such as Scale AI and Toloka use similar industry workflows involving data collection, annotation, and evaluation.&lt;br&gt;
What isn't shared are the specifics: the sensor setup, the calibration procedures, the annotation rubric, and the evaluation metrics. Those are where a company's IP lives. The pipeline shape? That's just the map. And the map is public.&lt;/p&gt;

&lt;p&gt;What I'd do differently: Simulate before you collect. Data collection is expensive — in time, hardware wear, and cognitive load on operators. Before running a full session, run a feasibility study with a small batch. Verify your sync and capture scripts. I assumed this was standard practice. It wasn't. The cost of that assumption showed up as rework.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Annotation Granularity: More Isn't Always Better
&lt;/h2&gt;

&lt;p&gt;There's a natural temptation when designing an annotation schema: capture everything. Every possible label, every edge case, every attribute you might want later. This is a trap.&lt;/p&gt;

&lt;p&gt;Over-granular annotation creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Higher operator cognitive load → slower throughput, more errors&lt;/li&gt;
&lt;li&gt;Greater inconsistency → more labels means more disagreement&lt;/li&gt;
&lt;li&gt;Fragile evaluation → you're evaluating against things you might not need&lt;/li&gt;
&lt;li&gt;The better approach? Start with the minimal viable schema that answers your core research question. Add complexity only when the data tells you it's necessary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A concrete heuristic: If you're designing labels for a demonstration-collection task, ask yourself: "Will I actually use this attribute to decide if the demonstration is successful?" If the answer isn't an immediate yes, drop it. Retrofitting granularity is easier than cleaning up inconsistent over-labeling.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Automation Still Needs Oversight—Especially in Evaluation
&lt;/h2&gt;

&lt;p&gt;Automation is seductive. The idea that you can "just set up an automated evaluation pipeline and let it run" is appealing, especially in a startup where resources are tight.&lt;br&gt;
I learned the hard way that automated evaluation is not a replacement for human judgment. It's a filter.&lt;/p&gt;

&lt;p&gt;Automated checks catch obvious failures (missing data, format errors).&lt;br&gt;
They don't catch subtle issues (context-dependent errors, edge cases).&lt;br&gt;
They create a false sense of confidence without regular spot-checks.&lt;br&gt;
The workflow that actually works: Run your automated eval, spot-check a sample of what passes, find patterns in the failures, add those to your automated checks, and repeat. It's the "human-in-the-loop" pattern. Easy to forget when you're moving fast.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Tell My Past Self
&lt;/h2&gt;

&lt;p&gt;If I could go back to Day 1 of that trial, here is exactly what I'd say:&lt;br&gt;
Ask for explicit success criteria upfront. "Good work" means different things to different people in different contexts. Don't assume alignment. Get it in writing, or at least in a clear verbal agreement.&lt;/p&gt;

&lt;p&gt;Simulate before you scale. A small batch of test data can save days of rework. Verify your entire pipeline end-to-end with a single example before you collect a hundred.&lt;br&gt;
Design for iteration, not perfection. Your first annotation schema will be wrong. That's fine. Make it easy to change. Don't fall in love with your first draft.&lt;/p&gt;

&lt;p&gt;Trust automation as a filter, not a replacement. It will never catch everything. Spot-check everything that matters, especially early on.&lt;br&gt;
After the fact, write yourself a retrospective — from memory, not from any retained materials. Capture what worked and what didn't while it's fresh. You'll thank yourself later when you're interviewing or planning your next project.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;Getting let go from a trial role is never fun. It stings, and there's always the temptation to replay the "why."&lt;/p&gt;

&lt;p&gt;But the technical lessons? Those are mine. They're transferable. They're not tied to any particular company's proprietary stack.&lt;br&gt;
A startup trial is an accelerated learning environment — whether it works out or not. The key is to separate what you learned about yourself and your craft from what you learned about that specific company's internal operations. The former is valuable, portable, and belongs to you.&lt;br&gt;
The latter is their business.&lt;/p&gt;

&lt;p&gt;Now, over to you:&lt;br&gt;
What's one technical or operational lesson from a short-term role that completely changed how you approach your work today? I'd genuinely love to hear your stories in the comments — drop one below. 👇&lt;/p&gt;

</description>
      <category>ai</category>
      <category>startup</category>
      <category>robotics</category>
      <category>productivity</category>
    </item>
    <item>
      <title>From Kilowatt-Hours to Carbon Counts: Shaping Accurate Energy Reporting with OED</title>
      <dc:creator>Johnny Santamaria</dc:creator>
      <pubDate>Wed, 10 Jun 2026 00:03:13 +0000</pubDate>
      <link>https://dev.to/jds64/from-kilowatt-hours-to-carbon-counts-shaping-accurate-energy-reporting-with-oed-4f99</link>
      <guid>https://dev.to/jds64/from-kilowatt-hours-to-carbon-counts-shaping-accurate-energy-reporting-with-oed-4f99</guid>
      <description>&lt;p&gt;"garbage in, garbage out" - Some quote I heard before&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Every building tells a story through its energy usage.&lt;/p&gt;

&lt;p&gt;A classroom left lit overnight, an HVAC system running longer than expected, or a sudden spike in electricity consumption across campus, these patterns often go unnoticed without the right tools to visualize them. That’s where the Open Energy Dashboard (OED) comes in.&lt;/p&gt;

&lt;p&gt;OED is an open-source platform that helps organizations transform raw utility data into meaningful insights through charts, analytics, and real-time monitoring. &lt;/p&gt;

&lt;p&gt;Schools, universities, sustainability teams, and facility managers use the dashboard to better understand how energy and resources are consumed across their buildings. Instead of sorting through spreadsheets or complicated meter data, users can quickly identify inefficiencies, compare trends, and make informed decisions that reduce both operational costs and environmental impact.&lt;/p&gt;

&lt;p&gt;The development build is similar to the production build. In order to run development, you must install Docker which is a container for images, like screenshots of a build you can run on your particular operating system. Here is the app running using ‘docker compose up’, and to quit the browser I used ‘docker compose down’.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Febho3xddkbe4qvo4o869.png" 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%2Febho3xddkbe4qvo4o869.png" alt="OED dev build" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Issue
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/OpenEnergyDashboard/OED/issues/962" rel="noopener noreferrer"&gt;issue link&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this contribution, I worked on LR18, an integration test focused on validating energy conversion behavior in the readings API. The test ensures that 15-minute interval energy readings can be correctly converted into kg CO₂ values while maintaining proper daily aggregation over large time ranges. While the change may seem small from the outside, it introduced interesting challenges involving fixture setup, unit conversions, expected CSV outputs, and backend testing consistency across the codebase.&lt;/p&gt;

&lt;p&gt;The following files and resources were the most important locations in the codebase for understanding and implementing the LR18 integration test:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;src/server/test/web/readingsLineMeterRangeQuantity.js This was the primary file where the LR18 test case was implemented. The file contains integration tests for line chart range (min/max) quantity meter readings across different aggregation ranges and unit behaviors. I used the surrounding LR1–LR7 tests to understand the expected structure, helper utilities, API request format, and CSV validation workflow. LR18 was added here because it specifically validates range aggregation behavior combined with unit conversion into kg CO₂.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/OpenEnergyDashboard/DesignDocs/blob/main/testing/testing.md" rel="noopener noreferrer"&gt;testing.md Documentation link&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This design document was important for understanding the intended testing requirements and naming conventions used throughout the project. The document outlines the required test scenarios for readings APIs, including line range (min/max) tests, aggregation expectations, and expected behavior across different unit conversions and time ranges. It helped confirm that LR18 belonged in the line range quantity test suite and clarified how expected CSV outputs should be structured.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;readings_ri_15_days_75.csv This readings dataset file contains the raw 15-minute interval test readings used for validating aggregation and conversion behavior. The LR18 test loads this dataset into the database during setup to simulate realistic meter readings over a long time range. These readings are later aggregated into daily points by the API.&lt;/li&gt;
&lt;li&gt;expected_line_range_ri_15_mu_kWh_gu_kgCO2_st_-inf_et_inf.csv This expected CSV file represents the correct API response output for LR18 after daily aggregation and unit conversion into kg CO₂. The test compares the API response against this file using helper assertions to verify timestamps, minimum/maximum values, and aggregation correctness.&lt;/li&gt;
&lt;li&gt;src/server/util/readingsUtils.js This utility file contains important helper functions and shared testing utilities used throughout the readings integration tests. Functions such as:&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;prepareTest()&lt;/li&gt;
&lt;li&gt;parseExpectedCsv()&lt;/li&gt;
&lt;li&gt;expectRangeToEqualExpected()&lt;/li&gt;
&lt;li&gt;getUnitId() were essential for seeding test fixtures, loading expected CSV data, resolving unit IDs dynamically, and validating API responses against expected outputs.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;src/server/test/web/readingsLineMeterQuantity.js This file was used as a reference implementation because it already contained an existing L18 conversion-based test. I compared its fixture setup, conversion seeding, and naming conventions against LR18 to ensure consistency between quantity tests and range quantity tests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://github.com/OpenEnergyDashboard/OED/blob/development/package.json" rel="noopener noreferrer"&gt;package.json&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The package.json file was useful for identifying available npm scripts and test commands used to run the OED test suite. This helped with running targeted test cases and validating the LR18 implementation locally during debugging.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;npm run testData This command was referenced in the testing documentation and used to load sample data into the system for validating the readings API behavior during local testing and debugging.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Product/Codebase
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Tech Stack
&lt;/h3&gt;

&lt;p&gt;Redux Toolkit (RTK): Manages the application's state, ensuring that data remains consistent and synchronized across all parts of the dashboard. Handles things like user selections, fetched meter data, UI state, etc.&lt;/p&gt;

&lt;p&gt;Plotly.js: Generates the interactive graphs and charts that visualize energy and resource consumption.&lt;/p&gt;

&lt;p&gt;React: The core frontend library used to build the user interface. It renders components for dashboards, selectors, and controls.&lt;/p&gt;

&lt;p&gt;Node.js: Runs the backend server, handling data API requests and managing business logic and authentication.&lt;/p&gt;

&lt;p&gt;PostgreSQL: Stores all historical energy/resource data, meter definitions, user configurations, etc. Acts as the main database.&lt;/p&gt;

&lt;p&gt;Express.js: Web server running on Node.js, exposes RESTful API endpoints for the frontend to request and update data.&lt;/p&gt;

&lt;p&gt;TypeScript: Provides type safety and improved developer experience for both frontend and backend codebases.&lt;/p&gt;

&lt;p&gt;Docker: Used to containerize and deploy the application and its dependencies for consistent environments (optional in some deployments).&lt;/p&gt;

&lt;h3&gt;
  
  
  Syatem diagram
&lt;/h3&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%2Fcewo42w92moe7ilvyrvg.png" 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%2Fcewo42w92moe7ilvyrvg.png" alt="System diagram of OED" width="800" height="376"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Use case
&lt;/h3&gt;

&lt;p&gt;“A user wants to see the CO₂ emissions for electricity usage over a selected date range.”&lt;/p&gt;

&lt;p&gt;Step-by-step walkthrough:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;User selects a building and a date range in the dashboard's UI (React) and clicks "View CO₂ Emissions."&lt;/li&gt;
&lt;li&gt;The React UI dispatches an action to Redux Toolkit to update the state with the new selection.&lt;/li&gt;
&lt;li&gt;Redux triggers an asynchronous operation (a thunk or saga) to fetch the relevant readings and emission data from the backend.&lt;/li&gt;
&lt;li&gt;The browser makes a REST API request to the Node.js/Express API, specifying the building and date range.&lt;/li&gt;
&lt;li&gt;The backend API receives the request. It queries the PostgreSQL database for all energy readings within the range and uses internal logic (data services) to convert those readings into kg CO₂, applying the project’s conversion formulas (the logic tested by the LR18 integration test).&lt;/li&gt;
&lt;li&gt;The backend returns a JSON object with time-stamped CO₂ emissions data to the frontend.&lt;/li&gt;
&lt;li&gt;The Redux store receives the data and the UI updates accordingly.&lt;/li&gt;
&lt;li&gt;Plotly.js renders an interactive chart with the emissions data, letting the user explore and analyze the results visually.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Challenge 1: Inconsistent Unit Conversion Setup in LR18
&lt;/h3&gt;

&lt;p&gt;One of the main technical challenges I faced was diagnosing why the LR18 integration test did not match the expected API behavior for converting readings into kg CO₂. Originally, the test was still requesting the kWh graphic unit, even though project requirements and the expected CSV output specified kg CO₂.&lt;/p&gt;

&lt;h3&gt;
  
  
  Self-Directed Problem-Solving Attempts
&lt;/h3&gt;

&lt;p&gt;Compared with Existing Tests: I reviewed LR1–LR7 to look for patterns in how units were handled and where API requests might be constructed differently.&lt;br&gt;
Codebase Search: I searched for kgCO2 and related conversion logic in both backend tests and the conversion fixture seeding code to find established best practices.&lt;br&gt;
Implementation Cross-Reference: I closely examined the relevant section in readingsLineMeterQuantity.js, focusing on how conversion fixtures were prepared and used, to spot differences.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Help
&lt;/h3&gt;

&lt;p&gt;I leveraged GitHub Copilot to reason through discrepancies, generating code suggestions and checking my test’s construction against unit-handling best practices.&lt;br&gt;
I carefully read the project’s testing.md guidelines to ensure my approach to data seeding and test assertions matched the intended design.&lt;/p&gt;

&lt;h3&gt;
  
  
  Status
&lt;/h3&gt;

&lt;p&gt;Resolved. The LR18 test setup was corrected: the test now requests the API with the correct kg CO₂ graphic unit and seeds the conversion fixtures to align with expectations. The test passes and provides accurate verification for future code changes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 2: CSV Naming and Expected Output Consistency
&lt;/h3&gt;

&lt;p&gt;I also struggled with inconsistent naming conventions for expected CSV files (kg_of_CO2 vs. kgCO2) when comparing actual output to reference data files. This led to confusion in test assertions and in locating previous examples for regression testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Self-Directed Problem-Solving Attempts
&lt;/h3&gt;

&lt;p&gt;Repository Search: I searched the repo for both naming styles to see which was most commonly used.&lt;br&gt;
Pattern Review: I surveyed surrounding tests (both quantity and range-based) to verify which format appeared in assertion logic and file loading calls.&lt;br&gt;
Assertion Validation: I checked the test utilities (such as readingsUtils.js) to see if there were helper functions that auto-resolved or validated expected outputs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Help
&lt;/h3&gt;

&lt;p&gt;I referenced conversion-based tests in readingsLineMeterQuantity.js to ensure consistency in both naming and output structure.&lt;/p&gt;

&lt;h3&gt;
  
  
  Status
&lt;/h3&gt;

&lt;p&gt;Resolved. I updated the LR18 test’s CSV file expectations and assertions to use the kgCO2 convention, eliminating confusion across the codebase and achieving a passing assertion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Challenge 3: Maintaining Consistent Branch Workflow (Using git cherry-pick)
&lt;/h3&gt;

&lt;p&gt;A key workflow challenge our team encountered was maintaining a consistent branch structure during development. There were instances when new commits were accidentally made to the wrong branch, which could cause confusion, code conflicts, or integration issues if left unaddressed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Self-Directed Problem-Solving Attempts
&lt;/h3&gt;

&lt;p&gt;Manual Merging: Attempted to merge the changes or re-apply the edits manually onto the correct branch, but this was time-consuming and could introduce mistakes.&lt;br&gt;
Branch Reset &amp;amp; Rebase: Explored using git reset and git rebase to move commits, but these approaches risked rewriting history or affecting unrelated changes.&lt;br&gt;
Git Documentation Review: Consulted the official Git docs and several online tutorials to look for a safer, more targeted solution for moving just the desired commit(s).&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Help
&lt;/h3&gt;

&lt;p&gt;Once I identified git cherry-pick as a possible approach, I confirmed the workflow with Copilot and checked with project teammates (if applicable) to ensure this was in line with team conventions.&lt;/p&gt;

&lt;h3&gt;
  
  
  Status
&lt;/h3&gt;

&lt;p&gt;Resolved. I successfully used git cherry-pick to copy the intended commit(s) to the correct branch without affecting unrelated work. This helped maintain branch hygiene and enabled a clean pull request history.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;To address the issue of ensuring accurate conversion of energy usage data to kilograms of CO₂ (LR18), I developed an integration test that simulates a user requesting CO₂ emissions data over a specific date range. I modeled my implementation outline using the structure and methodology established in existing test cases LR1–LR7, which demonstrated good practices for preparing test data, invoking the API endpoint, and checking results.&lt;/p&gt;

&lt;p&gt;In my solution, I set up sample meter readings in the database, triggered the backend logic to request the calculated CO₂ values, and verified that the backend (Node.js/Express) returned the correct results according to the expected conversion rules. This test helps ensure the data displayed in the dashboard (via Redux Toolkit and Plotly.js in the frontend) will always be calculated correctly, catching any regressions as the codebase evolves.&lt;/p&gt;

&lt;p&gt;I relied heavily on Redux Toolkit to manage the application state in the frontend, this ensures that, once the backend API returns the correct CO₂ data, the entire dashboard updates consistently and all visualizations generated by Plotly.js reflect accurate numbers.&lt;/p&gt;

&lt;p&gt;On the backend, I focused on the API logic and data services component (see System Diagram: Node.js/Express and Data Services) that handles the data transformation and communicates directly with the PostgreSQL database. My test verifies that conversions at this backend layer, before the data even reaches Redux or Plotly, are correct.&lt;/p&gt;

&lt;p&gt;I verified the solution using automated test runners via Mocha. After implementing the LR18 test, I ran the full test suite and confirmed that all tests, including my new one, passed. This proves that both existing and new requirements for data conversion are met.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq7olgj1ukzqpkpgc7ptt.png" 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%2Fq7olgj1ukzqpkpgc7ptt.png" alt="Testing solution with Mocha" width="799" height="248"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;See the full solution &lt;a href="https://github.com/OpenEnergyDashboard/OED/pull/1627" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>testing</category>
      <category>react</category>
      <category>typescript</category>
      <category>docker</category>
    </item>
    <item>
      <title>Keeping Your Secrets in Sync: Environment Lifecycle Management That Actually Works</title>
      <dc:creator>Johnny Santamaria</dc:creator>
      <pubDate>Sat, 14 Feb 2026 01:25:14 +0000</pubDate>
      <link>https://dev.to/jds64/keeping-your-secrets-in-sync-environment-lifecycle-management-that-actually-works-24ga</link>
      <guid>https://dev.to/jds64/keeping-your-secrets-in-sync-environment-lifecycle-management-that-actually-works-24ga</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Keyshade is an open-source real-time secret and configuration management platform that helps developers securely store, sync, and rotate sensitive data like API keys, passwords, and environment variables across multiple environments. It uses end-to-end encryption and live updates to eliminate insecure .env file sharing, manual dashboard updates, and secret sprawl. Designed for teams and solo developers alike, Keyshade improves collaboration, auditability, and security by providing version history, access controls, and seamless integrations with modern deployment platforms.&lt;/p&gt;

&lt;p&gt;Try it out on &lt;a href="https://keyshade.io/" rel="noopener noreferrer"&gt;https://keyshade.io/&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem: Silent Failures in Cloud Integrations (&lt;a href="https://github.com/keyshade-xyz/keyshade/issues/1254" rel="noopener noreferrer"&gt;#1254&lt;/a&gt;)
&lt;/h2&gt;

&lt;p&gt;The problem was that the integrations with Vercel or AWS Lambda would only respond to updates in secrets and variables. It would completely ignore situations where the user had deleted the environment or renamed the environment. This meant that when a user deleted an environment in Keyshade, the secrets and variables would still be available in Vercel or AWS Lambda but would be orphaned. This presented a problem from a security point of view. Also, when the environment was renamed, the integrations would be out of sync.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Bug Mattered to Production Teams
&lt;/h2&gt;

&lt;p&gt;This problem was significant because of several factors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Security Risk: When a user deleted an environment using Keyshade (like an environment in staging mode), the secrets became active in Vercel and AWS Lambda. This meant the secrets were exposed on another platform. That is, credentials became potential attack vectors.&lt;/li&gt;
&lt;li&gt;Data Integrity: The disconnect between Keyshade’s environment state and the external platforms led to a situation where users couldn’t be assured of data integrity. It defeats the purpose of having a reliable tool like Keyshade.&lt;/li&gt;
&lt;li&gt;User Experience: It will be the responsibility of the consumers of Keyshade to manually log in to the Vercel or AWS Lambda interface to clean up after environment changes, thereby defeating the purpose of the integration in the first place. Such an interface might discourage the use of Keyshade.&lt;/li&gt;
&lt;li&gt;Production Readiness: The integrations must be prepared to handle the full lifecycle of environments, not only the secrets within static environments, for Keyshade to be considered viable in production, especially by teams with high rates of environment changes (e.g., CI/CD workflows creating ephemeral environments). It was marked as "priority: high" and "difficulty: 4." This was my hardest PR that took around two months.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Under the Hood: Key Code Changes
&lt;/h2&gt;

&lt;p&gt;keyshade/apps/api/src/integrations/plugins/vercel.integration.ts&lt;br&gt;
Purposes: Integrates the Vercel platform - responsible for syncing Keyshade secrets/variables to Vercel projects as environment variables.&lt;br&gt;
Key sections to edit:&lt;br&gt;
getPermittedEvents(). Added ENVIRONMENT_UPDATED and ENVIRONMENT_DELETED to the set of events the integration subscribes to.&lt;br&gt;
added case handlers to switch of emitEvent() for routing environment lifecycle events.&lt;br&gt;
New functions added: handleEnvironmentDeleted(): This cleans up project-level environment variables scoped to the deleted environment and breaks the integration relationship handleEnvironmentUpdated(): Renames custom Vercel environments if available (pro plan users) &amp;amp; update integration metadata&lt;/p&gt;

&lt;p&gt;apps/api/src/integration/plugins/aws-lambda.integration.ts&lt;br&gt;
Purpose: Implements the AWS Lambda integration, syncing Keyshade secrets to Lambda function environment variables&lt;br&gt;
Key sections modified:&lt;br&gt;
getPermittedEvents(): Added the two environment events&lt;br&gt;
emitEvent() switch statement: Added routing for environment events&lt;br&gt;
New functions added:&lt;br&gt;
handleEnvironmentDeleted(): Removes environment-scoped keys from Lambda function configuration and disconnects the environment relationship&lt;br&gt;
handleEnvironmentUpdated(): No-op handler (Lambda has no environment rename concept) that logs an audit event&lt;/p&gt;

&lt;p&gt;apps/api/src/common/util.ts&lt;br&gt;
Purpose: Added retryWithBackoff() helper function to handle transient failures when calling external APIs (Vercel/AWS)&lt;br&gt;
Why important: Cloud APIs can fail temporarily; this ensures cleanup operations are resilient and retry with exponential backoff&lt;/p&gt;

&lt;p&gt;apps/api/src/integration/reconciler.ts&lt;br&gt;
Purpose: Lightweight reconciliation system that processes failed cleanup operations stored in integration.metadata.pendingCleanup&lt;br&gt;
Why important: When retries fail after multiple attempts, the reconciler can later replay these operations, ensuring eventual consistency even if external APIs are temporarily unavailable&lt;/p&gt;

&lt;p&gt;apps/api/src/event/event.types.ts&lt;br&gt;
Purpose: Defines TypeScript types for event metadata&lt;br&gt;
Relevant types:&lt;br&gt;
EnvironmentUpdatedEventMetadata: Contains name and description fields for environment updates&lt;br&gt;
EnvironmentDeletedEventMetadata: Contains the environment name for deletion events&lt;/p&gt;

&lt;p&gt;Test files:&lt;br&gt;
apps/api/src/integration/plugins/vercel.integration.spec.ts: tests covering environment lifecycle handlers&lt;br&gt;
apps/api/src/integration/plugins/aws-lambda.integration.spec.ts: tests covering Lambda-specific handlers&lt;br&gt;
apps/api/src/integration/reconciler.spec.ts: testing the reconciler's pending cleanup processing&lt;/p&gt;

&lt;p&gt;Supporting materials/artifacts&lt;br&gt;
Before the fix:&lt;br&gt;
When a user deleted an environment in Keyshade, the integration would:&lt;/p&gt;

&lt;p&gt;Not receive or process the ENVIRONMENT_DELETED event&lt;br&gt;
Leave all environment-scoped secrets active on Vercel/AWS Lambda&lt;br&gt;
Not update the integration metadata to reflect the environment was gone&lt;br&gt;
Create orphaned credentials that users would have to manually clean up&lt;/p&gt;

&lt;p&gt;Error behavior:&lt;br&gt;
While there wasn't a specific error message, the silent failure was the problem. Users would delete an environment expecting it to be cleaned up everywhere, but the secrets would persist on external platforms with no warning or indication that cleanup failed.&lt;br&gt;
After the fix:&lt;br&gt;
The integration now:&lt;br&gt;
Subscribes to ENVIRONMENT_UPDATED and ENVIRONMENT_DELETED events&lt;br&gt;
Automatically removes environment variables from Vercel projects when environments are deleted&lt;br&gt;
Removes environment-scoped keys from AWS Lambda function configurations&lt;br&gt;
Updates integration metadata to stay in sync with environment name changes&lt;br&gt;
Uses retry logic with exponential backoff for resilience against transient API failures&lt;br&gt;
Persists failed operations in pendingCleanup for later reconciliation&lt;br&gt;
Logs audit events for compliance and debugging&lt;/p&gt;

&lt;p&gt;Key Implementation Detail:&lt;br&gt;
For Vercel's ENVIRONMENT_UPDATED handler, there's a special consideration: Vercel only allows renaming custom environments on Pro plans, not the standard "Preview", "Production", or "Development" environments. The code includes logic to attempt the rename and gracefully handle cases where it's not supported.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;While preparing my pull request, I ran into several real-world challenges that highlight what contributing to an active open-source project actually looks like behind the scenes. The first issue was merge conflicts between my branch (attempt-1254) and the target develop branch. In a fast-moving repository with many contributors, this is almost inevitable. I resolved the conflicts by pulling the latest upstream changes and manually reviewing each conflicting section to understand both sides. My goal was to preserve other contributors’ work while ensuring my environment lifecycle handler implementation remained intact. After merging, I tested everything locally to confirm nothing regressed, then committed with a clear explanation of the resolution. It was a reminder that merge conflicts aren’t just mechanical fixes — they’re moments where you have to understand the intent of multiple developers and reconcile them thoughtfully.&lt;br&gt;
The next challenge surfaced during code review. A maintainer questioned my use of any type casts in TypeScript, particularly when accessing integration.id and working with the pendingCleanup metadata array. They pointed out that this could introduce long-term technical debt and suggested using @ts-expect-error instead. That feedback was valuable. While the any casts were originally a temporary measure to unblock a critical bug fix, the reviewer highlighted how @ts-expect-error is self-validating: TypeScript warns you if the suppressed error disappears later, which prevents stale workarounds from lingering in the codebase. I acknowledged the concern, added TODO comments recommending a future typed refactor, and committed to either replacing any with @ts-expect-error where appropriate or creating a follow-up issue to properly type the metadata structures. This exchange was a great example of how open-source review culture improves not just correctness, but long-term maintainability.&lt;br&gt;
The biggest blocker came from the CI/CD pipeline. My PR was failing two checks: a Snyk security scan and an API validation build. The frustrating part was that I couldn’t reproduce the failures locally. All my local tests passed — dependencies installed cleanly, the build succeeded, and every unit test ran successfully. But Snyk repeatedly failed in CI with errors related to workspace handling and package manager compatibility. The CLI didn’t fully support the project’s pnpm workspace setup in the same way the CI environment did, and I couldn’t access the GitHub Actions logs directly from my development environment. I tried multiple workarounds, including running Snyk through pnpm and downloading the standalone CLI, but nothing mirrored the CI behavior. Even rerunning the workflows with empty commits didn’t resolve the issue.&lt;br&gt;
At that point, I documented everything I had tried in a detailed PR comment and asked for help. A maintainer with admin access reviewed the CI logs and identified the root cause: a vulnerability in a transitive dependency that required updating the root package.json and regenerating the lockfile. They also explained that the project’s Snyk integration uses a specialized workspace configuration that isn’t trivial to replicate locally. This led to a broader discussion about improving CI transparency for external contributors, including the possibility of automatically surfacing relevant log snippets in PR comments. The experience reinforced an important lesson: as an external contributor, you won’t always have full visibility into infrastructure, and clear documentation of your debugging efforts is essential for maintainers to step in effectively.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution (&lt;a href="https://github.com/keyshade-xyz/keyshade/pull/1255" rel="noopener noreferrer"&gt;PR #1255&lt;/a&gt;)
&lt;/h2&gt;

&lt;p&gt;After working through these issues collaboratively, the pull request was successfully merged. The final solution included environment lifecycle event handlers for both Vercel and AWS Lambda, retry logic with exponential backoff, a reconciliation system for eventual consistency, and comprehensive test coverage. All CI checks passed, and review feedback was fully addressed. More importantly, the contribution closed a high-priority bug that had been blocking production adoption of cloud integrations. It was a strong reminder that persistence, careful communication, and respect for the review process are just as important as writing the code itself. Open-source success isn’t just technical, it’s collaborative.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Did I Learn?
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;How to read official Typescript documentation to break down confusing code&lt;/li&gt;
&lt;li&gt;How to ask beneficial questions to maintainers&lt;/li&gt;
&lt;li&gt;How to navigate CI/CD failures as an external contributor&lt;/li&gt;
&lt;li&gt;Understanding Event-Driven API Architecture&lt;/li&gt;
&lt;li&gt;Snyk improves security in development&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>opensource</category>
      <category>devops</category>
      <category>ci</category>
      <category>github</category>
    </item>
    <item>
      <title>Keyshade Debugging: Mastering Workspace Role Tests and API Repair</title>
      <dc:creator>Johnny Santamaria</dc:creator>
      <pubDate>Sat, 08 Nov 2025 01:06:38 +0000</pubDate>
      <link>https://dev.to/jds64/keyshade-debugging-mastering-workspace-role-tests-and-api-repair-1ok6</link>
      <guid>https://dev.to/jds64/keyshade-debugging-mastering-workspace-role-tests-and-api-repair-1ok6</guid>
      <description>&lt;p&gt;"A good tester prevents problems; a great tester finds them." &lt;strong&gt;Keith Klain, Director of Quality Engineering at KPMG UK&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Four disabled tests. One cryptic comment: 'flaky.' When I dove into Keyshade's workspace role tests, I discovered the failures weren't random at all—they revealed a fundamental misunderstanding of how the API validates permissions.&lt;/p&gt;

&lt;p&gt;Keyshade is a modern solution for secure, centralized credential and configuration management, designed for developers, DevOps engineers, and open-source communities who need to manage secrets without the headache.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Issue (&lt;a href="https://github.com/keyshade-xyz/keyshade/issues/1122" rel="noopener noreferrer"&gt;#1122&lt;/a&gt;)
&lt;/h2&gt;

&lt;p&gt;I decided to continue my open source journey on Keyshade since my blog post going over how I solved &lt;a href="https://www.linkedin.com/pulse/fixing-cli-error-handling-deep-dive-keyshades-bug-johnny-santamaria-au5yc" rel="noopener noreferrer"&gt;issue 998&lt;/a&gt;, which was that the Keyshade command line interface’s “keyshade run” command fails to give a proper error that users can understand. Now I decided to explore Keyshade’s API since I have explored how the CLI functions in its codebase.&lt;/p&gt;

&lt;p&gt;I found an issue where included a bug in workspace role tests, which meant the problem stemmed from how workspace roles were being set up and asserted in tests, some test fixtures and assertions depended on implicit ordering and shared state, which made the tests fragile and allowed timing or state-dependent flakes to slip into CI (Continuous Integration). By making the setup deterministic and isolating role state for each test (explicitly creating the roles and their relationships, avoiding shared mutable fixtures, and tightening assertions to check the actual role assignments rather than relying on incidental data), the tests now reliably exercise the API behavior they were intended to cover.&lt;/p&gt;

&lt;p&gt;The result is a more robust test suite that accurately guards against regressions in role handling for workspaces, reduces CI noise from flaky failures, and gives us greater confidence in future changes to authorization and workspace logic. Here are some key locations in the codebase we found out to be useful in solving the issue:&lt;/p&gt;

&lt;p&gt;The apps/api/src/workspace-role/ folder includes the workspace-role feature module — contains controller, service, DTOs, and possibly unit/integration tests. Focus on business logic, dependency wiring, and any in-module role constants.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Inspect the service methods (assignRole, removeRole, listRoles) for edge-case logic (no-op on duplicate role, default role applied on creation).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Check module exports and DI config: ensure the controller gets the expected implementation of the role service (mock vs real) in tests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Example check: verify if role constants are defined here (e.g., 'admin', 'member', 'viewer') and whether they match values used in DB/entities or frontend.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The apps/api/src/workspace-role/workspace-role-controller.ts file includes the controller / HTTP handlers for workspace-role endpoints — review request DTOs/validation, how the controller calls services, status codes, and permission checks performed at the controller layer.&lt;/p&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Example request mapping: POST /workspaces/:id/roles -&amp;gt; controller.assignRole(req.params.id, req.body). Check expected body shape (userId, role).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Example validation: if controller uses decorators or a schema, show the required fields (e.g., { userId: string, role: 'admin'|'member' }).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Example permission check: controller may call guard like ensureWorkspaceAdmin(currentUser, workspaceId) — confirm where that check happens and what error it throws (403 vs 401).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The apps/api/src/workspace-role/workspace-role/workspace-role.e2e.spec.ts file has end-to-end tests for workspace-role flows — tests that spin up the app (or a test server), seed DB data or fixtures, run real HTTP requests, and assert full-stack behavior and responses.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Test setup/teardown: show DB seeding (create users, create workspace) and cleanup steps to avoid cross-test leakage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Example test case: an e2e test that POSTs role assignment and then GETs membership list to assert the new role is present; include expected status codes and response shapes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Flakiness checks: look for timeouts, async waits, or hard-coded delays that may make these tests flaky (and thus failing intermittently).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;General Diagram Walkthrough of the Issue&lt;br&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%2Fdyjrp522srr83v1oolb7.png" 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%2Fdyjrp522srr83v1oolb7.png" alt="General Diagram Walkthrough of the Issue" width="799" height="102"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Reproducing the Problem
&lt;/h2&gt;

&lt;p&gt;Here are steps regarding how I reproduced the problem before assessing my plan, implementation, and solution&lt;/p&gt;

&lt;p&gt;Scenario 1 — should not be able to update the workspace role with environments that do not belong to the project Steps to reproduce the behaviour:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create (or use) a workspace W.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create Project A in W and capture PROJECT_A_ID.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create Project B in W and capture PROJECT_B_ID.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create Environment A in Project A (ENV_A_ID) and Environment B in Project B (ENV_B_ID).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a workspace role R for Project A that includes only ENV_A_ID.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure your CLI to target workspace W and authenticate with a token that can update roles.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use the CLI (or send an API request) to update role R to set environmentIds = [ENV_A_ID, ENV_B_ID].&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Observe the response/error shown by the CLI.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Expected behaviour&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The API should validate that ENV_B_ID does not belong to Project A and return a 4xx validation error (e.g., 400 or 422) with a clear, formatted message such as: "environment ENV_B_ID does not belong to project PROJECT_A_ID"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The CLI should print that formatted message to the user (no opaque stack trace or generic 500).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The update must be rejected and the role R must remain unchanged (still only ENV_A_ID).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Scenario 2 — should be able to add environment access for projects to the role with READ_WORKSPACE, UPDATE_WORKSPACE_ROLE, READ_PROJECT, READ_ENVIRONMENT authorities Steps to reproduce the behaviour:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create (or use) a workspace W.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create Project P in W and ensure it has at least one environment (ENV_P_1, …).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a workspace role R (it may initially have no project-level environment access).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Obtain an auth token that has the authorities: READ_WORKSPACE, UPDATE_WORKSPACE_ROLE, READ_PROJECT, READ_ENVIRONMENT.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure your CLI to use workspace W and that token.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use the CLI (or send an API request) to add project-level environment access for Project P to role R (the endpoint that grants project environment access).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Observe the response/error shown by the CLI and then GET the role R to inspect its environment access.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Expected behaviour&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The API should accept the request and return 200/201 with a clear success body describing the new project-level environment access (or list of environmentIds that were added).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The CLI should print a readable success message and show the role now has access to Project P environments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If the token lacks any required authority, the API should return 403 Forbidden with a clear formatted error and the CLI should print that message.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Scenario 3 — should be able to add environment access for projects to the role (permission variation) Steps to reproduce the behaviour:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create (or use) a workspace W, a Project X with environments, and a role R.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Prepare two auth tokens:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* AUTH\_ADMIN: token with UPDATE\_WORKSPACE\_ROLE (and related read authorities).

* AUTH\_RESTRICTED: token without UPDATE\_WORKSPACE\_ROLE.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Configure your CLI to use workspace W.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;With AUTH_ADMIN, run the CLI command (or API request) to add Project X environment access to role R. Verify success.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;With AUTH_RESTRICTED, attempt the same CLI command (or API request) to add Project X environment access to role R. Observe the response/error shown by the CLI.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Expected behaviour&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;With AUTH_ADMIN: API returns 200/201 and the role R gains access to Project X environments; CLI prints a readable success confirmation.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;With AUTH_RESTRICTED: API returns 403 Forbidden with a clear formatted message like "missing authority: UPDATE_WORKSPACE_ROLE" and the CLI prints that message; role R must not be modified.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Scenario 4 — should not be able to create workspace role where environments do not belong to the project Steps to reproduce the behaviour:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Create (or use) a workspace W.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create Project A and Project B in W.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create Environment B in Project B (ENV_B_ID).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Configure your CLI to target workspace W with a token that can create roles.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use the CLI (or POST API) to create a workspace role with:&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* projectId = PROJECT\_A\_ID

* environmentIds = \[ENV\_B\_ID\]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;Observe the response/error shown by the CLI.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Expected behaviour&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The API must reject the create request with a 4xx validation error (e.g., 400 or 422) and a clear message such as: "environment ENV_B_ID does not belong to project PROJECT_A_ID"&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The CLI should print that formatted error.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No role should be created.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Keyshade’s Codebase&lt;/p&gt;

&lt;p&gt;Here is Keyshade's codebase and API related to this issue, and how would I use the code to diagnose and fix the failing workspace-role tests, displaying the importance of code organization.&lt;/p&gt;

&lt;p&gt;High-level overview (relevant subsystems)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Schema / client shapes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;packages/schema/src/workspace-role/index.ts defines the WorkspaceRole payload shape (projects + environments arrays, authorities, etc.). That tells you the expected request/response shape for workspace role create/update operations.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;API service layer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;apps/api/src/workspace-role/workspace-role.service.ts contains the logic that parses project -&amp;gt; environment inputs and applies DB operations. The key function is parseProjectEnvironmentsDBOperation which:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;verifies that an environment slug exists and belongs to the provided project&lt;/li&gt;
&lt;li&gt;throws NotFoundException if an environment is not part of the project&lt;/li&gt;
&lt;li&gt;calls authorizationService.authorizeUserAccessToEnvironment to ensure the acting user has READ_ENVIRONMENT on that environment&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Authorization checks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;apps/api/src/auth/service/authorization.service.ts and apps/api/src/auth/service/authority-checker.service.ts implement the authority checks and higher-level authorization plumbing. authorizationService exposes methods such as authorizeUserAccessToEnvironment and authorizeUserAccessToSecret; these both call into the authority checker and also verify workspace membership.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tests &amp;amp; client usage:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;packages/api-client/tests/workspace-role.spec.ts contains tests that exercise the workspace-role APIs from a client perspective (create workspace, create project, create environments, then create/update roles).&lt;/li&gt;
&lt;li&gt;apps/platform and controller-instance reference the WorkspaceRoleController from the generated API client; you can use the API client or the CLI to reproduce e2e scenarios.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why the tests were failing (what the issue description lists)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The failing/disabled tests are focused on cases where environments provided for a workspace role do not belong to the project, and cases that require multiple authorities (READ_WORKSPACE, UPDATE_WORKSPACE_ROLE, READ_PROJECT, READ_ENVIRONMENT).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;The service code enforces two separate checks:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Ownership check: the environment must belong to the provided project — otherwise parseProjectEnvironmentsDBOperation throws NotFoundException with a message like "Environment X is not part of project Y".

2. Authorization check: after confirming the environment belongs to the project, the service calls authorizationService.authorizeUserAccessToEnvironment which requires the user to have READ\_ENVIRONMENT on that environment (and also checks workspace access).
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;If tests tried to use an environment that belongs to a different project, the service will intentionally return 404/NotFound before attempting to grant access; tests must assert that behavior.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If tests expected that adding environment access requires specific authorities, the test setup must give the test-user those authorities (or use a workspace-admin) before calling the endpoint.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;How to use the codebase to reproduce, diagnose, and fix the tests&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reproduce the failing case manually (fastest way to see precise error):&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* Use packages/api-client/tests/workspace-role.spec.ts as a reference for how tests set up the workspace/project/environment/role chain.

* From an integration/e2e test or an API client, create:

    * workspace A

    * project P1 inside workspace A

    * project P2 inside workspace A

    * environment E1 in P1 and environment E2 in P2

* Then attempt to create/update a workspace role that includes project P1 but lists environment E2 (an environment that belongs to P2).

* Observe the response: workspace-role.service.ts is expected to detect the mismatch and return a 404 Not Found with message "Environment ... is not part of project ...".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;Check the authority path:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* If you provide a correct project-&amp;amp;gt;environment mapping, but the requestor lacks READ\_ENVIRONMENT (or other expected authorities like UPDATE\_WORKSPACE\_ROLE), authorizationService.authorizeUserAccessToEnvironment will throw Unauthorized/Forbidden. Ensure test user has the required authorities when the test expects success.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;Fix the tests (common issues &amp;amp; fixes):&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* If a test was disabled because it used an environment from a different project but expected a different error (or success), restore the test and:

    * Either adjust the test to expect NotFound (because parseProjectEnvironmentsDBOperation intentionally rejects cross-project environments).

    * Or adjust test fixtures so the environment is created in the same project as the project entry in the request.

* For authority-related tests:

    * Make sure the test user has the necessary authorities before calling the API. Grant a role to the test user with READ\_WORKSPACE, UPDATE\_WORKSPACE\_ROLE, READ\_PROJECT, READ\_ENVIRONMENT when the test expects role creation/update to succeed.

    * If a test is verifying denial paths (should not be able to create/update), ensure the user has fewer/missing authorities and assert the API returns Unauthorized/Forbidden appropriately.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;Where to change code if the behavior is incorrect:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* If you decide the server should validate authority first (instead of ownership first), modify parseProjectEnvironmentsDBOperation ordering; but be careful: the current behavior (check membership/ownership first, then authorization) is consistent with tests that expect NotFound for cross-project envs.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;If messages or status codes are inconsistent with tests, adjust the thrown exceptions in parseProjectEnvironmentsDBOperation (NotFoundException) or in authorizationService (Unauthorized/Forbidden) to align with the test expectations — but prefer changing tests to reflect correct domain behavior unless an API bug is proven.&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Useful files to inspect or instrument:&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;* packages/schema/src/workspace-role/index.ts (payload schema)

* apps/api/src/workspace-role/workspace-role.service.ts (parseProjectEnvironmentsDBOperation and create/update logic)

* apps/api/src/auth/service/authorization.service.ts (authorizeUserAccessToEnvironment)

* apps/api/src/auth/service/authority-checker.service.ts (lower-level authority checks)

* packages/api-client/tests/workspace-role.spec.ts (test setup and expected assertions)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Concrete actionable checklist to resolve the issue in the repository&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Re-enable the disabled tests.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;For each disabled test, verify which of the two server behaviors it is validating:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ownership validation (environment belongs to project) → expect 404 Not Found. Ensure test builds a request with a mis-matched environment and asserts a 404 and the message text if desired.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Authorization validation (user needs the listed authorities) → ensure test grants the required authorities to the requester when expecting success; when expecting failure, ensure the user lacks at least one required authority and assert Unauthorized/Forbidden.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run the tests locally (or in CI):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The workspace-role e2e test file: packages/api-client/tests/workspace-role.spec.ts is a good starting point.&lt;/li&gt;
&lt;li&gt;Add logging in workspace-role.service.ts parseProjectEnvironmentsDBOperation if you need to trace why an environment was rejected.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If the test failure shows unexpected status codes or messages, adjust the service's thrown exceptions to match the documented API or update the tests to assert the documented behavior.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;If you change server code, add unit tests in apps/api for parseProjectEnvironmentsDBOperation to cover:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;environment not in project → NotFound&lt;/li&gt;
&lt;li&gt;environment in project but user lacks READ_ENVIRONMENT → Unauthorized/Forbidden&lt;/li&gt;
&lt;li&gt;environment in project and user has authority → success path produces correct DB ops&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Short example of the expected behavior (mapping to the tests listed in the issue)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;"should not be able to update the workspace role with environments that do not belong to the project":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Call update role with project P1 + env from P2 → server should return 404 Not Found (environment not part of project).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;"should be able to add environment access for projects to the role with READ_WORKSPACE, UPDATE_WORKSPACE_ROLE, READ_PROJECT, READ_ENVIRONMENT authorities":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ensure requestor has those authorities (assign a role in test fixture) → call create/update and assert success and that the role contains the project-&amp;gt;environment mapping in response.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;"should be able to add environment access for projects to the role":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Same as above but likely using normal granted authorities; ensure fixtures grant appropriate permissions.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;"should not be able to create workspace role where environments do not belong to the project":&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create role payload with mismatched environment → expect 404 Not Found.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where to look in code to make changes or confirm behavior&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;parseProjectEnvironmentsDBOperation in:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;apps/api/src/workspace-role/workspace-role.service.ts&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;authorization plumbing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;apps/api/src/auth/service/authorization.service.ts&lt;/li&gt;
&lt;li&gt;apps/api/src/auth/service/authority-checker.service.ts&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;request/response schema:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;packages/schema/src/workspace-role/index.ts&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;tests:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;packages/api-client/tests/workspace-role.spec.ts&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Use Case
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Goal: Create/update a workspace role that grants access to specific projects and, within those projects, specific environments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Client payload shape (what the API expects)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. name, description, authorities (array), projectEnvironments: \[{ projectSlug, environmentSlugs: \[slug\] }\]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Server behavior (what the code enforces)&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. For each projectEnvironments entry, resolve projectSlug -&amp;amp;gt; projectId (getProjectSlugToIdMap).

2. For each environmentSlug in environmentSlugs:

    * Verify that environmentSlug exists AND belongs to that project (prisma.environment.findFirst where slug + projectId). If not, throw NotFoundException (404) with a body "Environment not found" / "Environment X is not part of project Y".
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Authorize the acting user has READ_ENVIRONMENT for that environment (authorizationService.authorizeUserAccessToEnvironment). This also verifies workspace-level access and permitted authorities (via authorityCheckerService).&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Finally, upsert projectWorkspaceRoleAssociation rows connecting roleId&amp;lt;&amp;gt;projectId and connect the environment slugs.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Why the workspace-role tests were failing&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;The failing/disabled tests covered two distinct types of validation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ownership validation: environment must belong to the project in the request. If not, server should reject with 404 (Not Found).&lt;/li&gt;
&lt;li&gt;Authorization validation: even if environment belongs to project, the acting user must have proper authorities (READ_ENVIRONMENT, UPDATE_WORKSPACE_ROLE, READ_PROJECT, READ_WORKSPACE_ROLE) to add environment access to a role.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Typical causes for failures:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tests that assert 404 were previously commented out or used incorrect fixtures (e.g., environment not created, or created but in same project), so the server behavior didn't match test expectations.&lt;/li&gt;
&lt;li&gt;Tests that assert successful updates weren't ensuring the test actor had the required authorities (so server returned Unauthorized/401/403 instead of 200).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;My PR re-enabled those tests and fixed fixtures by:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating environments in the correct projects (dev, stage).&lt;/li&gt;
&lt;li&gt;Explicitly setting the membership role authorities (e.g., update the workspace member role to include READ_ENVIRONMENT and UPDATE_WORKSPACE_ROLE) when the test expected updates to succeed.&lt;/li&gt;
&lt;li&gt;Using consistent test header 'x-e2e-user-email' (alice.email) for requests.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Walkthrough of the two failing test categories and what to change&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;"Environment doesn't belong to the project" (ownership)&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Server side: parseProjectEnvironmentsDBOperation checks project membership:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It runs prisma.environment.findFirst({ where: { slug: environmentSlug, projectId } })&lt;/li&gt;
&lt;li&gt;If not found, it throws: throw new NotFoundException(constructErrorBody('Environment not found', Environment ${environmentSlug} is not part of project ${pe.projectSlug}))&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tests expecting 404 must:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a workspace and two projects in that workspace.&lt;/li&gt;
&lt;li&gt;Ensure the environment used in the request was created in THE OTHER project, or not created at all (but prefer creating in other project to avoid false positives).&lt;/li&gt;
&lt;li&gt;Make a POST/PUT with projectSlug set to projectA and environmentSlugs containing an environment slug that actually belongs to projectB.&lt;/li&gt;
&lt;li&gt;Assert statusCode === 404.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;"Adding environment access requires specific authorities" (authorization)&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Server side: after ownership check, parseProjectEnvironmentsDBOperation calls: await this.authorizationService.authorizeUserAccessToEnvironment({ user, slug: environmentSlug, authorities: [Authority.READ_ENVIRONMENT] })&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;That code path will call authorityCheckerService.checkAuthorityOverEnvironment which checks the acting user's permitted authorities for that environment (it can be from project-level or workspace-level roles).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Tests expecting success must ensure the acting user has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;READ_WORKSPACE (if required by the flow), UPDATE_WORKSPACE_ROLE, READ_PROJECT, READ_ENVIRONMENT — in practice, the tests can mutate an existing workspace role for the test user to include these authorities and also connect appropriate project/environment associations so the user has project-scoped authority.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Exact test changes — enable one disabled test and adjust fixture&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Below is a concrete enabled test snippet for packages/api-client/tests/workspace-role.spec.ts that re-enables the “should not be able to create workspace role where environments do not belong to the project” scenario.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This patch enables the test and makes the fixture explicit: create two projects, create an environment in projectB, then attempt to create a workspace role that references projectA + the environment slug from projectB. The test asserts 404.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;packages/api-client/tests/workspace-role.spec.ts&lt;/p&gt;

&lt;p&gt;// (This is the test snippet to add/enable within the existing test suite)&lt;/p&gt;

&lt;p&gt;it('should not be able to create workspace role where environments do not belong to the project', async () =&amp;gt; {&lt;/p&gt;

&lt;p&gt;// Precondition: two projects exist in the workspace&lt;/p&gt;

&lt;p&gt;// projects[0] -&amp;gt; projectA, projects[1] -&amp;gt; projectB (test fixture established earlier in the file)&lt;/p&gt;

&lt;p&gt;// Create an environment in projectB that should NOT be valid for projectA&lt;/p&gt;

&lt;p&gt;await prisma.environment.create({&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Notes about placement:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Put this enabled test near the other workspace-role create tests after the workspace + project fixtures are created and available.&lt;/li&gt;
&lt;li&gt;Ensure projects variable references are the ones created during beforeAll/beforeEach steps in that test file.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A directly analogous enabled e2e test (what PR 1156 did)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;PR 1156 enabled very similar tests in apps/api/src/workspace-role/workspace-role.e2e.spec.ts — it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Created dev/stage environments in the proper projects,&lt;/li&gt;
&lt;li&gt;Updated the Member workspace role to add project associations and required authorities for the actor,&lt;/li&gt;
&lt;li&gt;Re-enabled the previously commented tests and asserted status codes 200 or 404 as appropriate.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Small server-side unit test to lock in parseProjectEnvironmentsDBOperation behavior&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;I recommend adding a unit test that calls parseProjectEnvironmentsDBOperation (or exercises the public create/update API) with a project-environment mismatch and asserts a NotFoundException is thrown.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Example test file (unit) to confirm rejection for cross-project environment:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;apps/api/src/workspace-role/workspace-role.service.spec.ts&lt;/p&gt;

&lt;p&gt;import { Test, TestingModule } from '@nestjs/testing'&lt;/p&gt;

&lt;p&gt;import { WorkspaceRoleService } from './workspace-role.service'&lt;/p&gt;

&lt;p&gt;import { AuthorizationService } from '@/auth/service/authorization.service'&lt;/p&gt;

&lt;p&gt;import { PrismaService } from '@/prisma/prisma.service'&lt;/p&gt;

&lt;p&gt;import { NotFoundException } from '@nestjs/common'&lt;/p&gt;

&lt;p&gt;Proposed server code changes — minimal / optional&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The current server code in parseProjectEnvironmentsDBOperation is already correct about ownership-first validation (it checks slug + projectId before calling authorizeUserAccessToEnvironment). The PR you provided does not require a behavior change on the server — tests should reflect the server semantics.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you did want to change server behavior, the main two options are:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Keep current semantics (ownership check first, then authorization). Pros: deterministic 404 for cross-project envs; avoids running authorization queries for environment slugs that don't belong to the provided project. Cons: reveals that the environment slug exists in a different project (server replies 404 with message referencing project mismatch).

2. Run authorization first (i.e., check the caller's permissions to read the environment before checking project membership). Pros: could collapse some error channels and avoid disclosing cross-project existence if the caller lacks permission. Cons: often you still need to fetch the environment by slug (so you still learn about its existence) — and the authorization check typically requires fetching the environment anyway to know project/workspace id. It also changes the status codes returned for the cross-project case (may return 401 instead of 404), which is a breaking API semantics change for clients that currently rely on 404 in that case.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Recommendation: do NOT change the ordering of checks unless there's a clear security/privacy reason. The current behavior (404 on ownership mismatch) matches the tests in PR 1156 and is explicit. Prefer to fix test fixtures and grant/revoke authorities in test setup rather than altering server semantics.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Why the PR's approach (what you already did in PR 1156) is correct&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;You re-enabled tests and fixed fixtures instead of changing server semantics. Specifically:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Created missing environments in the appropriate projects before calling the API.&lt;/li&gt;
&lt;li&gt;Updated the test user/role authorities where tests expected success.&lt;/li&gt;
&lt;li&gt;Asserted the correct status codes: 404 for ownership mismatch and 200 for successful updates when authorities exist.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;This is minimal and non-breaking.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Trade-offs if you change server behavior (ownership vs authorization ordering)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Changing the order to authorize first:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;May return 401/403 instead of 404 for the cross-project environment case — tests and clients must be updated.&lt;/li&gt;
&lt;li&gt;Could obscure whether an environment exists in another project (slight security benefit).&lt;/li&gt;
&lt;li&gt;Could introduce additional database lookups if you need to check membership as well; may not yield significant performance gain.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Keeping ownership-first (current):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clear, deterministic 404 response for mismatched environment ownership.&lt;/li&gt;
&lt;li&gt;Tests remain simple: assert 404 for the cross-project case.&lt;/li&gt;
&lt;li&gt;Slight information disclosure (you show that environment exists but in a different project), which may be acceptable in an internal system like Keyshade.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Summary of exact artifacts to change (concrete)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Add/enable the test in packages/api-client/tests/workspace-role.spec.ts as above (or in apps/api/src/workspace-role/workspace-role.e2e.spec.ts — PR 1156 already did the same).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a small unit test in apps/api/src/workspace-role/workspace-role.service.spec.ts to lock in behavior of parseProjectEnvironmentsDBOperation for cross-project envs (example above).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ensure any tests that expect successful updates explicitly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;create the environments in the projects they want to reference&lt;/li&gt;
&lt;li&gt;grant the test actor role(s) the required authorities (UPDATE_WORKSPACE_ROLE, READ_PROJECT, READ_ENVIRONMENT) before calling create/update&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;I reviewed PR #1156 and walked the tests and service code. Below I list concrete challenges we encountered while making this PR and practical mitigations. I narrate what I checked and what I'd recommend next.&lt;/p&gt;

&lt;p&gt;What I did (quick recap)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Inspected the workspace-role service logic (parseProjectEnvironmentsDBOperation) and the authorization flow.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Reviewed the test files that were re-enabled/modified in the PR (workspace-role e2e/spec files).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Confirmed the PR fixes were basically test-fixture and authority-setup issues (creating environments in right projects, granting the test user required authorities), not major server-behavior changes.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Verified the PR merged successfully but noted a set of practical challenges that often show up in this work.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Challenges encountered (with examples and mitigations)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Test fixture brittleness and DB state coupling&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Problem: E2E tests depend on precise workspace/project/environment setup and roles. Small differences in fixture creation order or leftover DB rows cause flakiness.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Example: A test assumed "production" env exists in projectA but CI used a different test run that already had production in projectB -&amp;gt; unexpected 200 instead of 404.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mitigation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Always create and assert fixtures in test setup (do not assume names).&lt;/li&gt;
&lt;li&gt;Use randomized slugs (e.g., slug-&amp;lt;timestamp&amp;gt;-&amp;lt;random&amp;gt;) to avoid collisions across parallel CI jobs.&lt;/li&gt;
&lt;li&gt;Add teardown/cleanup that removes created rows or run tests inside isolated databases/transactions.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Authority/permission complexity in tests&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Problem: Tests that check success paths require several authorities (READ_WORKSPACE, UPDATE_WORKSPACE_ROLE, READ_PROJECT, READ_ENVIRONMENT). If the test doesn't explicitly grant them, the server returns Unauthorized and the test fails.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Example: Test enabling environment access but not updating the Member role to include UPDATE_WORKSPACE_ROLE.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mitigation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Explicitly set up the actor’s roles/authorities per test case.&lt;/li&gt;
&lt;li&gt;Add helper functions in tests for "grantMemberAuthoritiesForEnvironmentAccess" to make fixtures clear.&lt;/li&gt;
&lt;li&gt;Keep negative/positive authority tests separate and deterministic.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Merge conflicts (simple but annoying)&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Problem: Enabling tests and modifying the same test files or fixtures on different branches led to conflicts (e.g., two branches each uncommented different blocks of tests).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Example: Two branches changed apps/api/src/workspace-role/workspace-role.e2e.spec.ts: one enabled a test, another refactored a beforeEach. Merge resulted in conflict markers around large commented blocks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mitigation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Rebase frequently on the develop branch to reduce long-lived divergences.&lt;/li&gt;
&lt;li&gt;Split large test-enabling changes into smaller PRs (enable + fix fixtures + authority wiring).&lt;/li&gt;
&lt;li&gt;When resolving conflicts, run the full test suite locally before pushing the merge commit.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Creating temporary test files or helper tests "to be removed later" causing issues&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Problem: Developers often create throwaway tests / temporary files to debug behavior and forget to remove them. These can be picked up by CI and cause nondeterministic runs or duplicate test names.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Example: Added apps/api/src/workspace-role/tmp-debug.spec.ts to iterate quickly, later removed locally but remained in another branch → CI fails or duplicate DB setup occurs.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mitigation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Avoid committing throwaway test files. If necessary, keep them in local-only branches or protect with a naming convention excluded from CI.&lt;/li&gt;
&lt;li&gt;Add CI test discovery rules to ignore test files matching tmp-*.spec.ts, if you must have temporary tests.&lt;/li&gt;
&lt;li&gt;Review diffs carefully before commit and run git status to ensure no debug/test files are included.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Test duplication across packages (api-client vs apps/api)&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Problem: Similar workspace-role tests existed in both packages/api-client/tests and apps/api e2e test files; enabling one copy and forgetting to update the other caused inconsistent behavior.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Example: One test in packages/api-client used the API client and different headers than the e2e server-side test; runtime differences led to mismatched assertions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mitigation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Consolidate canonical e2e tests in one place (prefer apps/api e2e that hits the server directly), keep api-client tests focused on client behavior.&lt;/li&gt;
&lt;li&gt;If duplication is unavoidable, keep test expectations aligned and use shared helpers/mocks.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Race conditions in setup/cleanup (parallel tests)&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Problem: Creating projects/environments concurrently across parallelized test jobs (or even within same job) created timing-based failures.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mitigation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Make tests idempotent and isolate data per test.&lt;/li&gt;
&lt;li&gt;Use unique slugs per test run.&lt;/li&gt;
&lt;li&gt;Consider serializing tests that share DB resources or use test DB instances per worker.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Error/status-code semantics and client expectations&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Problem: Tests rely on specific status codes (404 vs 401). If server-side code ordering changes (ownership check vs authorization check) the codes change and many tests break.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Example: If you move authorization first, a cross-project environment case might return 401 instead of 404.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mitigation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep server semantics stable; if a change is required, update all tests and document the change.&lt;/li&gt;
&lt;li&gt;Add unit tests that assert the contract (e.g., cross-project environment -&amp;gt; NotFound).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;CI flakiness from environment variables and headers&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Problem: Some tests rely on headers like 'x-e2e-user-email'. If CI environment variable email differs or parallel runs reuse the same email, tests can mutate shared state.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mitigation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generate a test-specific email per run (e.g., alice+&amp;lt;run-id&amp;gt;@keyshade.xyz).&lt;/li&gt;
&lt;li&gt;Make test harness read and propagate headers from the same fixture source.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Commit noise and history (multiple tiny commits while fixing test)&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Problem: WIP commits to enable tests, debug, revert, add files, remove files created a messy branch and bigger diffs, increasing review overhead and merge conflicts.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mitigation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Squash local fixup commits before opening PR or use interactive rebase to tidy commits.&lt;/li&gt;
&lt;li&gt;Keep PRs small and focused: one PR for enabling tests and fixtures, another for refactors.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;ol&gt;
&lt;li&gt;Accidental API contract changes vs test fixes&lt;/li&gt;
&lt;/ol&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Problem: When tests fail, there’s a temptation to change server code to make tests pass instead of fixing tests. That can mask real bugs or break clients.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mitigation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Prefer fixing tests/fixtures if the server behavior matches documented contract.&lt;/li&gt;
&lt;li&gt;If server needs change, write unit tests to cover new contract and get reviewer approval.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Concrete, practical steps I recommend next&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Add a small checklist to PR templates for test-enabling PRs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Confirm unique slugs in fixtures&lt;/li&gt;
&lt;li&gt;Confirm actor authorities are set explicitly&lt;/li&gt;
&lt;li&gt;Run full test suite locally (or run the specific e2e group) before pushing&lt;/li&gt;
&lt;li&gt;Clean up any temporary test files&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add a helper in tests for deterministic unique slugs and user emails to avoid collisions in CI.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Consider adding a lint/CI guard that fails the build when "tmp-*.spec.ts" or "debug" test files are found in the commit.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you expect future merges to touch the same big e2e files, coordinate branches or split PRs (e.g., create "enable tests" PR and a follow-up "refactor fixtures" PR).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;Here is a summarized plan of my solution:&lt;/p&gt;

&lt;p&gt;Lock the contract with unit tests by adding unit tests that assert parseProjectEnvironmentsDBOperation rejects cross-project environment slugs with NotFound. This prevents accidental server reordering from changing semantics. Make tests deterministic by ensuring every test explicitly creates its fixtures (workspace, project, environment) and set unique slugs per run, which will explicitly grant the required authorities to the test actor in success-path tests.&lt;/p&gt;

&lt;p&gt;Prevent accidental temporary test files by adding the pre-commit hook above and a CI check to refuse pushes that include tmp/debug test files. Keep good merge strategies and PR hygiene by keeping PRs small and focused (enable tests separately from refactors). Make sure to rebase frequently and squash debug commits and add a PR/TODO checklist that requires: unique slugs, explicit authority setup, no temporary files, and local e2e run before requesting review. Consolidate test suites by deciding canonical location for workspace-role e2e tests (prefer apps/api e2e) and remove duplicates, or centralize test helpers. Here is my final PR solving the issue over a month:  &lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/keyshade-xyz/keyshade/pull/1156" rel="noopener noreferrer"&gt;PR #1156&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;In conclusion, Keyshade’s clean, well-structured codebase and deterministic tests make development faster, safer, and more collaborative. Clear intent, consistent conventions, and reliable CI build trust between maintainers and contributors—enabling focused changes, quicker reviews, and confident releases.&lt;/p&gt;

</description>
      <category>api</category>
      <category>devbugsmash</category>
      <category>typescript</category>
      <category>cybersecurity</category>
    </item>
    <item>
      <title>Fixing CLI Error Handling: A Deep Dive into Keyshade's WebSocket Communication Bug</title>
      <dc:creator>Johnny Santamaria</dc:creator>
      <pubDate>Tue, 12 Aug 2025 15:00:00 +0000</pubDate>
      <link>https://dev.to/jds64/fixing-cli-error-handling-a-deep-dive-into-keyshades-websocket-communication-bug-489m</link>
      <guid>https://dev.to/jds64/fixing-cli-error-handling-a-deep-dive-into-keyshades-websocket-communication-bug-489m</guid>
      <description>&lt;p&gt;“If you build software, every error message is marketing” - &lt;strong&gt;Jason Fried, &lt;a href="https://www.goodreads.com/work/quotes/6928276" rel="noopener noreferrer"&gt;Rework&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In today’s fast-paced software development landscape, managing credentials and sensitive configurations securely is more critical—and more challenging—than ever. Dispersed secrets, manual processes, and unplanned storage not only slow teams down but also increase the risk of breaches and operational errors. &lt;strong&gt;Keyshade&lt;/strong&gt; addresses these challenges head-on with a centralized, automated approach to credential and configuration governance. Purpose-built for engineering teams, DevOps practitioners, startups, and open-source maintainers. Keyshade streamlines secret management while enhancing security, collaboration, and efficiency.&lt;/p&gt;

&lt;p&gt;Keyshade is an advanced platform for centralized credential and configuration governance, designed to safeguard sensitive data, eliminate human error, and streamline operational complexity. By automating secret management, it fortifies security posture, amplifies cross-team collaboration, and liberates developers from the inefficiencies of fragmented secret storage. Its primary constituency—engineering teams, DevOps practitioners, early-stage startups, and open-source maintainers—adopts Keyshade to mitigate secret proliferation, minimize security liabilities, and embed robust secret orchestration seamlessly into their development pipelines. Users gravitate toward Keyshade for its ability to render secret management secure, intuitive, and collaborative, without imposing friction upon established workflows or toolchains. Below is a flow of control diagram discussing how users would typically use the product:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyn04j9jpb4md0z634g4o.png" 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%2Fyn04j9jpb4md0z634g4o.png" alt=" " width="800" height="858"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user can send commands via keyshades terminal or on keyshades web app
&lt;/li&gt;
&lt;li&gt;On the web, the user can sign up, and initialize projects within workspaces

&lt;ul&gt;
&lt;li&gt;Users can do the same in the &lt;a href="https://docs.keyshade.xyz/getting-started/setting-up-your-profile" rel="noopener noreferrer"&gt;CLI&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Once the a project is made a http request is made to the API websocket service
&lt;/li&gt;
&lt;li&gt;The websocket service then retrieves that data to put in the Keyshade’s database to save the data for future use&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Keyshade is currently in alpha and not live so there really are not any users as of posting this.&lt;/p&gt;

&lt;h2&gt;
  
  
  The issue
&lt;/h2&gt;

&lt;p&gt;I partnered with a member of the Computing Talent Initiative with the task of solving &lt;a href="https://github.com/keyshade-xyz/keyshade/issues/998" rel="noopener noreferrer"&gt;issue 998&lt;/a&gt; in Keyshade for a Summer Open Source Experience sponsored by . The main problem we noticed was that the Keyshade command line interface’s “keyshade run” command fails to give a proper error that users can understand. We would want to extract an error message and output it to the user. The issue is a bug that seems urgent to solve right now for users to understand errors they might make when working with Keyshade’s CLI, which is the main feature of Keyshade. Here are some key locations in the codebase we found out to be useful in solving the issue:&lt;/p&gt;

&lt;h3&gt;
  
  
  run.command.ts
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Handles CLI logic for running commands, including WebSocket client interactions
&lt;/li&gt;
&lt;li&gt;Within a CLI folder&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  change-notifier.socket.ts
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Contains the handleRegister method, which processes the register-client-app event
&lt;/li&gt;
&lt;li&gt;Handles authentication, authorization, and client registration logic
&lt;/li&gt;
&lt;li&gt;Emits the client-registered response to the client and logs errors
&lt;/li&gt;
&lt;li&gt;Within an API folder&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  run.types.d.ts
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;ClientRegisteredResponse describes the shape of the response sent by the server after registration (success, message)
&lt;/li&gt;
&lt;li&gt;Ensures type safety for client-side code handling server responses
&lt;/li&gt;
&lt;li&gt;Within a CLI folder&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here are steps regarding how we reproduced the problem before finding the important locations in the codebase&lt;/p&gt;

&lt;p&gt;Steps to reproduce the behaviour:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a project on keyshade
&lt;/li&gt;
&lt;li&gt;Configure your CLI to tap into this project
&lt;/li&gt;
&lt;li&gt;Delete the project
&lt;/li&gt;
&lt;li&gt;Use keyshade run &amp;lt;&amp;gt; command
&lt;/li&gt;
&lt;li&gt;See the error&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Expected behaviour
&lt;/h2&gt;

&lt;p&gt;We would want the CLI to display what actually went wrong. The API should send the formatted message to the CLI. The CLI should print the error.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fis4b0st0wzgdaml8xn3a.png" 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%2Fis4b0st0wzgdaml8xn3a.png" alt="Reproduced issue" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Keyshade’s Codebase
&lt;/h2&gt;

&lt;p&gt;Before delving into the problem itself, and after pinpointing the critical components, we took a step back to develop a comprehensive understanding of the codebase’s architecture. Keyshade’s backend is architected with &lt;strong&gt;NestJS&lt;/strong&gt; (a progressive Node.js framework) and &lt;strong&gt;TypeScript&lt;/strong&gt;, leveraging &lt;strong&gt;Prisma&lt;/strong&gt; as its database ORM, interfacing with a &lt;strong&gt;PostgreSQL&lt;/strong&gt; relational database. It also integrates &lt;strong&gt;Redis&lt;/strong&gt; for high-performance caching and publish/subscribe messaging, as well as &lt;strong&gt;Socket.IO&lt;/strong&gt; for real-time, bidirectional communication between its command line interface and application programming interface.&lt;/p&gt;

&lt;p&gt;On the frontend, the stack comprises &lt;strong&gt;React&lt;/strong&gt;, &lt;strong&gt;TypeScript&lt;/strong&gt;, &lt;strong&gt;JavaScript&lt;/strong&gt;, and &lt;strong&gt;Next.js&lt;/strong&gt;, presumably to power a performant and SEO-friendly web interface. Development workflows are streamlined through &lt;strong&gt;Nx&lt;/strong&gt; (for monorepo orchestration), &lt;strong&gt;ESLint&lt;/strong&gt; and &lt;strong&gt;Prettier&lt;/strong&gt; (for code quality and style enforcement), alongside the TypeScript compiler for rigorous static type checking.&lt;/p&gt;

&lt;p&gt;For security, Keyshade employs &lt;strong&gt;Elliptic Curve Cryptography&lt;/strong&gt; for robust public-key encryption, &lt;strong&gt;JWT&lt;/strong&gt; for token-based authentication, and API key authentication for service-to-service communication. The infrastructure is containerized via &lt;strong&gt;Docker&lt;/strong&gt; and designed with cloud deployment in mind. A high-level system diagram encapsulates the overall architecture of the codebase as follows:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnx6hcxpex283fibc2l77.png" 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%2Fnx6hcxpex283fibc2l77.png" alt="Keyshade system diagram" width="799" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is a use case, using the components in the diagram to give you a better understanding of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use Case: Developer Fetching Live Configuration Updates
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Step 1: Developer Initiates CLI Command
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;keyshade run \--workspace my-company \--project web-app \--environment production

What happens: The developer runs the CLI command to start their application with live configuration updates from Keyshade.

The CLI documentation can be found [here](https://docs.keyshade.xyz/getting-started/installing-the-cli)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Step 2: CLI Processes Command (run.command.ts)
&lt;/h3&gt;

&lt;p&gt;The run.command.ts file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parses the command arguments (workspace, project, environment)
&lt;/li&gt;
&lt;li&gt;Reads the local configuration file to get API credentials
&lt;/li&gt;
&lt;li&gt;Validates the required parameters using types from run.types.d.ts
&lt;/li&gt;
&lt;li&gt;Prepares to establish WebSocket connection for live updates&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Step 3: WebSocket Connection Establishment
&lt;/h3&gt;

&lt;p&gt;The CLI creates a websocket connection:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;const socket = io('ws://api.keyshade.xyz/change-notifier')&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What happens: The client connects to the NestJS backend's WebSocket gateway (change-notifier.socket.ts).&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 4: Client Registration Request
&lt;/h3&gt;

&lt;p&gt;The CLI emits the registration event:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;socket.emit('register-client-app', {  workspaceSlug: 'my-company',  projectSlug: 'web-app',   environmentSlug: 'production'});&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;What happens: This triggers the handleRegister method in &lt;a href="http://change-notifier.socket.ts" rel="noopener noreferrer"&gt;change-notifier.socket.ts&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Step 5a: Authentication
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const userContext \= await this.extractAndValidateUser(client)  
Extracts API key from WebSocket headers (x-keyshade-token)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Queries PostgreSQL database via Prisma to validate the API key
&lt;/li&gt;
&lt;li&gt;Verifies user account is active&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 5b: Authorization checks
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;await this.authorizationService.authorizeUserAccessToWorkspace({...})await this.authorizationService.authorizeUserAccessToProject({...})await this.authorizationService.authorizeUserAccessToEnvironment({...})&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Checks if user has READ_WORKSPACE, READ_VARIABLE, READ_SECRET permissions&lt;br&gt;&lt;br&gt;
Validates access to the specific project&lt;br&gt;&lt;br&gt;
Confirms environment access rights&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5c: Client Registration
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;await this.addClientToEnvironment(client, environment.id)  
Creates entry in PostgreSQL mapping socket ID to environment ID
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;Adds client to Redis set for real-time notifications&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 6: Success Response
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;What happens: Server sends confirmation back to CLI using the ClientRegisteredResponse interface from run.types.d.ts.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Step 7: Configuration Fetching
&lt;/h3&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;After successful registration:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;CLI fetches initial configuration values (secrets, variables)
&lt;/li&gt;
&lt;li&gt;Application starts with these environment variables
&lt;/li&gt;
&lt;li&gt;CLI continues listening for real-time updates&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 8: Real-Time Updates (Ongoing)
&lt;/h3&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Another developer updates a secret value through the web interface.  

await this.redisSubscriber.subscribe(CHANGE\_NOTIFIER\_RSC, this.notifyConfigurationUpdate.bind(this))  
The web interface publishes change to Redis channel
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;All API instances receive the notification&lt;/p&gt;

&lt;p&gt;this.server.to(clientId).emit('configuration-updated', data)&lt;br&gt;&lt;br&gt;
Server identifies all clients registered for that environment&lt;br&gt;&lt;br&gt;
Sends update notification to active CLI instances&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;CLI receives the configuration-updated event&lt;br&gt;&lt;br&gt;
Updates local environment variables&lt;br&gt;&lt;br&gt;
Application continues running with new values&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;One specific technical challenge I encountered while attempting to solve issue 998 was figuring out why my changes in change-notifier.socket.ts were not being reflected when I tested them through the CLI. Despite updating the logic inside the Socket.IO handlers, the CLI behavior remained unchanged, and it was unclear whether the API changes were even being registered. Here is an attempt to solve this challenge and other challenges I encountered.&lt;/p&gt;

&lt;p&gt;So I tried debugging the issue after reproducing the issue and found the debug information for keyshade issue 998&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;@johnny603 ➜ /workspaces/keyshade (teamAttempt/#998) $ DEBUG=* keyshade run "echo 'test'"INFO: Checking API key validity...INFO: API key is valid!INFO: Connecting to socket...socket.io-client:url parse wss://api.keyshade.xyz/change-notifier +0mssocket.io-client new io instance for wss://api.keyshade.xyz/change-notifier +0mssocket.io-client:manager readyState closed +0mssocket.io-client:manager opening wss://api.keyshade.xyz/change-notifier +0msengine.io-client:socket creating transport "websocket" +0msengine.io-client:socket options: {"path":"/socket.io/","agent":false,"withCredentials":false,"upgrade":true,"timestampParam":"t","rememberUpgrade":false,"addTrailingSlash":true,"rejectUnauthorized":true,"perMessageDeflate":{"threshold":1024},"transportOptions":{},"closeOnBeforeunload":false,"autoConnect":false,"extraHeaders":{"x-keyshade-token":"ks_a3adff2a9db8bf3c42fc817880ac4125983b83c171f0d04d"},"transports":[null],"hostname":"api.keyshade.xyz","secure":true,"port":"443","query":{"EIO":4,"transport":"websocket"},"socket":{"binaryType":"nodebuffer","writeBuffer":[],"_prevBufferLen":0,"_pingInterval":-1,"_pingTimeout":-1,"_maxPayload":-1,"_pingTimeoutTime":null,"secure":true,"hostname":"api.keyshade.xyz","port":"443","transports":["websocket"],"_transportsByName":{},"opts":{"path":"/socket.io/","agent":false,"withCredentials":false,"upgrade":true,"timestampParam":"t","rememberUpgrade":false,"addTrailingSlash":true,"rejectUnauthorized":true,"perMessageDeflate":{"threshold":1024},"transportOptions":{},"closeOnBeforeunload":false,"autoConnect":false,"extraHeaders":{"x-keyshade-token":"ks_a3adff2a9db8bf3c42fc817880ac4125983b83c171f0d04d"},"transports":[null],"hostname":"api.keyshade.xyz","secure":true,"port":"443"},"readyState":"opening"}} +0msengine.io-client:socket setting transport websocket +6mssocket.io-client:manager connect attempt will timeout after 20000 +8msengine.io-client:socket socket receive: type "open", data "{"sid":"qxJTaYpvOE4a6kIaAAB8","upgrades":[],"pingInterval":25000,"pingTimeout":20000,"maxPayload":1000000}" +756msengine.io-client:socket socket open +0mssocket.io-client:manager open +755mssocket.io-client:manager cleanup +0mssocket.io-client:socket transport is open - connecting +0mssocket.io-client:manager writing packet {"type":0,"nsp":"/change-notifier"} +1mssocket.io-parser encoding packet {"type":0,"nsp":"/change-notifier"} +0mssocket.io-parser encoded {"type":0,"nsp":"/change-notifier"} as 0/change-notifier, +0msengine.io-client:socket flushing 1 packets in socket +1msengine.io-client:socket starting upgrade probes +1msengine.io-client:socket socket receive: type "message", data "0/change-notifier,{"sid":"OpbhAQ0UYcfklhBoAAB9"}" +249mssocket.io-parser decoded 0/change-notifier,{"sid":"OpbhAQ0UYcfklhBoAAB9"} as {"type":0,"nsp":"/change-notifier","data":{"sid":"OpbhAQ0UYcfklhBoAAB9"}} +251mssocket.io-client:socket socket connected with id OpbhAQ0UYcfklhBoAAB9 +251mssocket.io-client:manager writing packet {"type":2,"data":["register-client-app",{}],"options":{"compress":true},"nsp":"/change-notifier"}+251mssocket.io-parser encoding packet {"type":2,"data":["register-client-app",{}],"options":{"compress":true},"nsp":"/change-notifier"} +0mssocket.io-parser encoded {"type":2,"data":["register-client-app",{}],"options":{"compress":true},"nsp":"/change-notifier"} as 2/change-notifier,["register-client-app",{}] +0msengine.io-client:socket flushing 1 packets in socket +1mssocket.io-client:socket draining queue +1msengine.io-client:socket socket receive: type "message", data "2/change-notifier,["client-registered",{"success":false,"message":{"response":{"message":"{\"header\":\"Workspace not found\",\"body\":\"Workspace undefined not found\"}","error":"Not Found","statusCode":404},"status":404,"options":{},"message":"{\"header\":\"Workspace not found\",\"body\":\"Workspace undefined not found\"}","name":"NotFoundException"}}]" +261mssocket.io-parser decoded 2/change-notifier,["client-registered",{"success":false,"message":{"response":{"message":"{\"header\":\"Workspace not found\",\"body\":\"Workspace undefined not found\"}","error":"Not Found","statusCode":404},"status":404,"options":{},"message":"{\"header\":\"Workspace not found\",\"body\":\"Workspace undefined not found\"}","name":"NotFoundException"}}] as {"type":2,"nsp":"/change-notifier","data":["client-registered",{"success":false,"message":{"response":{"message":"{\"header\":\"Workspace not found\",\"body\":\"Workspace undefined not found\"}","error":"Not Found","statusCode":404},"status":404,"options":{},"message":"{\"header\":\"Workspace not found\",\"body\":\"Workspace undefined not found\"}","name":"NotFoundException"}}]} +262mssocket.io-client:socket emitting event ["client-registered",{"success":false,"message":{"response":{"message":"{\"header\":\"Workspace not found\",\"body\":\"Workspace undefined not found\"}","error":"Not Found","statusCode":404},"status":404,"options":{},"message":"{\"header\":\"Workspace not found\",\"body\":\"Workspace undefined not found\"}","name":"NotFoundException"}}] +261msERROR: Error registering to API: [object Object]&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I came to the conclusion that the client is sending an empty object {} in the registration message, but the server expects a ChangeNotifierRegistration object with workspace, project, and environment slugs. This results in undefined values being passed to the authorization check, causing the “Workspace undefined not found” error. The code correctly creates the registrationData object with workspaceSlug, projectSlug, and environmentSlug&lt;br&gt;&lt;br&gt;
But the debug output shows it’s sending an empty object {}&lt;/p&gt;

&lt;p&gt;This suggests that the data parameter being passed to connectToSocket has undefined values for workspace, project, and environment. Our team reached out to the issue maintainer radjip-b with a question regarding our process of the issue so far.&lt;/p&gt;

&lt;p&gt;This small technical challenge was solved.&lt;/p&gt;

&lt;h2&gt;
  
  
  Solution
&lt;/h2&gt;

&lt;p&gt;The solution for Keyshade’s issue 998 took five and a half weeks to complete during the eight-week internship. In the remaining time, we addressed additional related issues, including issue &lt;a href="https://github.com/keyshade-xyz/keyshade/issues/989" rel="noopener noreferrer"&gt;989&lt;/a&gt; and &lt;a href="https://github.com/keyshade-xyz/keyshade/issues/988" rel="noopener noreferrer"&gt;988&lt;/a&gt;, which involved adding Slack and Discord integration documentation, as well as contributing to &lt;a href="https://github.com/keyshade-xyz/keyshade/pull/1093" rel="noopener noreferrer"&gt;PR 1093&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Changes Introduced by PR #1093:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Slack Integration Documentation&lt;/strong&gt;: A brand-new &lt;strong&gt;“How It Works”&lt;/strong&gt; modal was added to the Keyshade platform’s UI, providing a visual, step-by-step guide—including screenshots—on how to set up Slack integration. This includes selecting Slack in the Integrations tab, entering credentials (signing secret, Bot token, channel ID), and testing with sample secrets and variables.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Modal Improvements &amp;amp; Developer Feedback&lt;/strong&gt;: The modal component underwent adjustments—such as replacing a bare &amp;lt;DialogClose asChild /&amp;gt; (which caused rendering issues) with a proper close button, and revising instructions for better clarity (“Bot Token from Slack”). Additionally, an unused function (_handleClose) was cleaned up and textual instructions were streamlined.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Discord Integration Documentation&lt;/strong&gt;: The scope of the PR was expanded to include documentation for Discord integration as well—hence the updated title to “feat: added slack and discord integration docs.”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Internal Routing and Formatting Refactor&lt;/strong&gt;: Responding to reviewer feedback, the team refactored the integration documentation routing logic—removing the DOCS_SLUG_MAP, simplifying routing, standardizing layout for project creation and event triggers, and cleaning up the formatting and positioning of images.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Lint Fixes&lt;/strong&gt;: Minor linting errors (such as extraneous braces in index.tsx) were corrected to ensure code consistency and avoid build issues.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Summary Table&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Area&lt;/th&gt;
&lt;th&gt;What Changed&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Docs &amp;amp; UI&lt;/td&gt;
&lt;td&gt;“How It Works” modal with guide and screenshots&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Component Cleanup&lt;/td&gt;
&lt;td&gt;Dialog close functionality fixed, redundant code removed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Discord Docs&lt;/td&gt;
&lt;td&gt;Documentation for Discord integration added&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slack Docs&lt;/td&gt;
&lt;td&gt;Documentation for Slack integration added&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Documentation Routing&lt;/td&gt;
&lt;td&gt;Routing logic simplified, layout standardized, visuals aligned&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Code Quality&lt;/td&gt;
&lt;td&gt;Linting issues resolved&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Solution Description For Issue #998
&lt;/h3&gt;

&lt;p&gt;Our team resolved a bug where the socket notifier and run command error handling would display [object Object] instead of a meaningful error message. The root cause was improper formatting of the error object before logging it to the user. The fix ensures that error messages are now properly stringified or formatted, providing clearer and more helpful feedback during CLI failures. To verify the fix, you can run the keyshade run command with an invalid configuration or project setup that triggers a connection error. This update significantly improves the developer experience by making error outputs more understandable.&lt;/p&gt;

&lt;p&gt;We determined that only three files required direct changes to address the issue:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;NestJS WebSocket Gateway&lt;/strong&gt; – The server-side component uses NestJS’s @WebSocketGateway decorator with Socket.IO to handle real-time communication between the CLI and API server. The handleRegister method in change-notifier.socket.ts processes client registration requests and can return different error message formats depending on the failure type (authentication, authorization, or database errors).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CLI Command Handler&lt;/strong&gt; – The run.command.ts file contains the client-side logic that connects to the WebSocket server and handles the registration response. This component already included robust error parsing logic capable of handling both string and object formats, but the TypeScript interface was preventing proper type checking.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Type Checker&lt;/strong&gt; – The run.types.d.ts file was updated to allow both string and object types, since the server can send either format.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;After the maintainer approved the fix, we made additional updates to the codebase by modifying two more files: changelog.md to document the changes, and package.json to bump the semantic version to &lt;strong&gt;3.2.3&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is in the file changes
&lt;/h3&gt;

&lt;p&gt;These were changes found using git’s diff command&lt;/p&gt;

&lt;p&gt;What has been changed can also be found &lt;a href="https://github.com/keyshade-xyz/keyshade/pull/1087/files" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;changelog.md:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;🐛 Bug Fixes* **api, cli:** ambiguous error messages on keyshade run failure ([#1087](&lt;a href="https://github.com/keyshade-xyz/keyshade/issues/1087)" rel="noopener noreferrer"&gt;https://github.com/keyshade-xyz/keyshade/issues/1087)&lt;/a&gt;) ([206dda7](&lt;a href="https://github.com/keyshade-xyz/keyshade/commit/206dda702b3f3f0cabeb73e4f74dd2ccfc962631)" rel="noopener noreferrer"&gt;https://github.com/keyshade-xyz/keyshade/commit/206dda702b3f3f0cabeb73e4f74dd2ccfc962631)&lt;/a&gt;)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;apps/cli/package.json: "version": "3.2.2-stage.2",&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;apps/cli/src/types/command/run.types.d.ts:&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;message: string | object // Allow both string and object types since server can send either&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;apps/cli/src/commands/run.commands.ts:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Accurate WebSocket URL Parsing:&lt;br&gt;
The CLI’s socket connection logic now uses the full host from the base URL (via the URL class), ensuring reliable and correct WebSocket connections especially for complex URLs. &lt;/p&gt;

&lt;p&gt;Registration Timeout Added:A 30-second timeout was introduced for socket registration. If the server does not respond within this window, users receive a clear error message and the process exits gracefully. This prevents hanging connections and improves user feedback. &lt;/p&gt;

&lt;p&gt;Enhanced Error Message Extraction:The registration response error handling is now more robust: It intelligently extracts meaningful error messages whether the server responds with a string, an object, or a nested structure. Attempts to parse JSON error details for more informative output. Falls back to stringifying unknown error types, ensuring users always get useful feedback. All error messages are now logged in a consistent, user-friendly format. &lt;/p&gt;

&lt;p&gt;Minor Cleanup:Removed a redundant blank line after the socket connection initiation for improved code readability.   &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;apps/api/src/socket/change-notifier.socker.ts: &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;1. Improved Error Handling &amp;amp; Feedback The registration process now provides much more detailed error messages to clients. Instead of a generic error or raw message, errors are parsed, logged, and emitted in user-friendly formats. &lt;/p&gt;

&lt;p&gt;2. Custom Authentication Logic for Socket Connections The previous decorator-based guards (@UseGuards(AuthGuard, ApiKeyGuard) and @RequiredApiKeyAuthorities) have been replaced by custom methods. New methods (extractAndValidateUser and convertToAuthenticatedUser) manually handle authentication and authority checks, mimicking guard behavior but allowing for more granular error handling and feedback. &lt;/p&gt;

&lt;p&gt;3. API Key Authority Checks The code now checks if the API key has the required authorities (READ_WORKSPACE, READ_PROJECT, and READ_ENVIRONMENT) directly within the socket handler. If the user has ADMIN authority, individual checks are bypassed. &lt;/p&gt;

&lt;p&gt;4. Enhanced Logging Connection logs now include the presence or absence of authentication headers. Errors during registration are logged with more context, including error types and constructors. &lt;/p&gt;

&lt;p&gt;5. Dependency Injection Updates The SlugGenerator service is now injected into the class, preparing for potential future use or slug-related operations. &lt;/p&gt;

&lt;p&gt;6. Cleaned-Up Imports Removed unused decorators and guards, reflecting the shift to manual authentication and authority verification. &lt;/p&gt;

&lt;p&gt;7. User Context Construction The code now constructs a detailed user context object for socket connections, including IP address, workspace, and API key authorities, improving downstream authorization and personalization.&lt;/p&gt;

&lt;h3&gt;
  
  
  Testing the solution
&lt;/h3&gt;

&lt;p&gt;While testing Keyshade, I encountered an issue that was easy to reproduce: create a project in Keyshade, configure your CLI to connect to it, delete the project, and then run the keyshade run &amp;lt;&amp;gt; command. This sequence would trigger an unclear error message, leaving the cause ambiguous. The expected behavior was for the CLI to clearly display the actual problem, with the API sending a properly formatted error message that the CLI would then print for the user. After applying the solution, the CLI behaved as intended, providing a clear and informative error message, making the debugging process much smoother.&lt;/p&gt;

&lt;p&gt;Another testing approach involved building custom WebSocket test clients designed to send invalid authentication data, deliberately triggering error conditions to assess system resilience. We closely monitored server-side logs to ensure errors were correctly detected and handled, while also verifying that the client could parse various error message formats. This end-to-end validation—from CLI input, through WebSocket communication and API processing, to the final CLI response—confirmed the integrity and reliability of the entire flow. This comprehensive process not only confirmed the integrity and reliability of the entire system but also successfully closed issue #998 with our &lt;a href="https://github.com/keyshade-xyz/keyshade/pull/1087" rel="noopener noreferrer"&gt;PR #1087&lt;/a&gt;.&lt;/p&gt;

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

&lt;p&gt;We resolved a complex WebSocket bug by fixing authentication handling, type safety, and error messaging across the backend and CLI. They used systematic debugging, targeted fixes to three core components, and custom test clients to replace vague errors with clear feedback, improve authentication timeouts, and update integration docs—showcasing technical excellence, thorough testing, and a user-focused, collaborative approach.&lt;/p&gt;

</description>
      <category>opensource</category>
      <category>devbugsmash</category>
      <category>security</category>
      <category>programming</category>
    </item>
    <item>
      <title>Product sense - Intro to Keyshade</title>
      <dc:creator>Johnny Santamaria</dc:creator>
      <pubDate>Sat, 12 Jul 2025 02:42:42 +0000</pubDate>
      <link>https://dev.to/jds64/product-sense-intro-to-keyshade-707</link>
      <guid>https://dev.to/jds64/product-sense-intro-to-keyshade-707</guid>
      <description>&lt;p&gt;Product sense is the intuitive understanding of what makes a product successful—the ability to identify user needs, spot market gaps, and build solutions that people actually want to use. It's the difference between building features and building the right features.&lt;/p&gt;

&lt;p&gt;At its core, product sense combines three critical elements: deep empathy for users, keen awareness of market dynamics, and the judgment to prioritize what matters most. It's what separates products that solve real problems from those that collect digital dust.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Anatomy of Strong Product Sense
&lt;/h2&gt;

&lt;p&gt;Product sense isn't just about having good ideas—it's about having the right ideas at the right time. It manifests in several ways:&lt;br&gt;
User-Centric Thinking: Understanding not just what users say they want, but what they actually need. This means digging beneath surface-level requests to uncover underlying pain points.&lt;/p&gt;

&lt;p&gt;Market Awareness: Recognizing where opportunities exist in the competitive landscape and how user behavior is evolving.&lt;br&gt;
Execution Wisdom: Knowing which features to build first, how to sequence development, and when to pivot based on feedback.&lt;/p&gt;

&lt;h2&gt;
  
  
  Keyshade: Product Sense in Action
&lt;/h2&gt;

&lt;p&gt;Keyshade exemplifies strong product sense by addressing a fundamental yet often overlooked developer pain point: secure environment variable management. While many developers resort to scattered .env files or basic cloud solutions, Keyshade's creators recognized that teams needed something more robust yet accessible.&lt;/p&gt;

&lt;p&gt;The product sense behind Keyshade reveals itself in several key decisions:&lt;br&gt;
Solving a Real Problem: Rather than building another generic developer tool, Keyshade targets the specific friction developers face when managing secrets across environments. This isn't a nice-to-have—it's a daily necessity that most teams handle poorly.&lt;br&gt;
Open Source Strategy: By making Keyshade open source, the team demonstrates understanding of developer culture and trust requirements. When it comes to handling sensitive data, transparency isn't just appreciated—it's essential.&lt;br&gt;
Developer-First Design: The focus on seamless integration and straightforward workflows shows awareness of how developers actually work. Complex security tools often go unused because they create more friction than they solve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters for Product Builders
&lt;/h2&gt;

&lt;p&gt;Keyshade's approach illustrates how product sense translates into tangible decisions. Instead of building a comprehensive DevOps platform (which would be feature-rich but unfocused), they identified one specific workflow that teams consistently struggle with and built a targeted solution.&lt;br&gt;
This focused approach is classic product sense—recognizing that doing one thing exceptionally well often beats doing many things adequately. It's the difference between building what's technically possible and building what's strategically valuable.&lt;br&gt;
The lesson for product builders is clear: strong product sense isn't about having all the answers upfront. It's about asking the right questions, staying close to user problems, and having the discipline to build solutions that matter rather than solutions that impress.&lt;/p&gt;

&lt;p&gt;Product sense develops through experience, but it starts with genuine curiosity about user problems. Tools like Keyshade succeed because they're built by people who understand both the technical challenges and the human workflows they're trying to improve.&lt;/p&gt;

</description>
      <category>security</category>
      <category>opensource</category>
      <category>programming</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Johnny Santamaria</dc:creator>
      <pubDate>Mon, 23 Jun 2025 23:22:28 +0000</pubDate>
      <link>https://dev.to/jds64/-33oh</link>
      <guid>https://dev.to/jds64/-33oh</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/ben/meme-monday-jg8" class="crayons-story__hidden-navigation-link"&gt;Meme Monday&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/ben" class="crayons-avatar  crayons-avatar--l  "&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1%2Fbabb96d0-9cd2-49bc-a412-2dc4caf94c2a.png" alt="ben profile" class="crayons-avatar__image" width="800" height="800"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/ben" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Ben Halpern
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Ben Halpern
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png" width="166" height="102"&gt;&lt;/a&gt;
                &lt;img alt="Community Curator" class="community-leader-icon" src="https://assets.dev.to/assets/community-leader-icon-b72c9e74eff54916e5c46c962f47ba40c9f611a71f8b157511f9613f69c0001b.svg" width="20" height="20"&gt;
              
              &lt;div id="story-author-preview-content-2617574" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/ben" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1%2Fbabb96d0-9cd2-49bc-a412-2dc4caf94c2a.png" class="crayons-avatar__image" alt="" width="800" height="800"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Ben Halpern&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/ben/meme-monday-jg8" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Jun 23 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/ben/meme-monday-jg8" id="article-link-2617574"&gt;
          Meme Monday
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag crayons-tag--filled  " href="/t/discuss"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;discuss&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/jokes"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;jokes&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/watercooler"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;watercooler&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/ben/meme-monday-jg8" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/exploding-head-daceb38d627e6ae9b730f36a1e390fca556a4289d5a41abb2c35068ad3e2c4b5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/multi-unicorn-b44d6f8c23cdd00964192bedc38af3e82463978aa611b4365bd33a0f1f4f3e97.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;61&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/ben/meme-monday-jg8#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              69&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            1 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>discuss</category>
      <category>jokes</category>
      <category>watercooler</category>
    </item>
    <item>
      <title>Level Up Your Linux Skills</title>
      <dc:creator>Johnny Santamaria</dc:creator>
      <pubDate>Mon, 23 Jun 2025 23:17:19 +0000</pubDate>
      <link>https://dev.to/jds64/level-up-your-linux-skills-301e</link>
      <guid>https://dev.to/jds64/level-up-your-linux-skills-301e</guid>
      <description>&lt;h2&gt;
  
  
  Deep Dive into OverTheWire Bandit
&lt;/h2&gt;

&lt;p&gt;“So many books, so little time.” - &lt;strong&gt;Frank Zappa&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;How a gamified approach to cybersecurity education transforms learning from tedious to thrilling&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In today’s fast-paced digital world, cybersecurity skills are no longer optional — they’re essential. But learning them through static lessons, long-winded lectures, or dry textbooks? That often leads to burnout or boredom.&lt;/p&gt;

&lt;p&gt;That’s where &lt;strong&gt;OverTheWire Bandit&lt;/strong&gt; comes in — a brilliant learning platform that transforms traditional Linux security training into a hands-on, puzzle-based game. It’s not just engaging — it’s effective, because it teaches you by doing.&lt;/p&gt;

&lt;p&gt;I started playing Bandit right after finishing a web development class, where I had learned how to navigate files and search through directories using Linux. Jumping into this wargame felt like leveling up — taking those foundational skills and applying them in a way that was both fun and deeply practical.&lt;/p&gt;

&lt;p&gt;Unlike passive learning platforms, Bandit drops you straight into a live Linux environment. Every command has consequences. Every file might contain a hidden clue. Every level challenges you to solve real problems with real tools — just like in the field.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Makes Bandit Special?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The Progressive Learning Model
&lt;/h3&gt;

&lt;p&gt;Bandit follows a carefully crafted progression that mirrors real-world security scenarios. Each level builds upon the previous one, introducing new concepts while reinforcing established skills. This isn't just random command execution – it's a structured journey through the fundamentals of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;File system navigation and permissions&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Text processing and pattern matching&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Network protocols and services&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cryptography and encoding&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Process management and privilege escalation&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Version control systems&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Real Environment, Real Skills
&lt;/h3&gt;

&lt;p&gt;What sets Bandit apart from simulated environments is its authenticity. You're working with actual Linux systems, real SSH connections, and genuine security tools. The skills you develop aren't theoretical – they're immediately applicable to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;System administration tasks&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security auditing and penetration testing&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Digital forensics investigations&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;DevOps and automation workflows&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technical Highlights: Key Learning Concepts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  File System Mastery
&lt;/h3&gt;

&lt;p&gt;Early levels focus on fundamental file operations, but with security-minded twists. You'll encounter:&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;# Working with special characters in filenames&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; ./-

&lt;span class="c"&gt;# Handling spaces in filenames&lt;/span&gt;
&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="s2"&gt;"spaces in this filename"&lt;/span&gt;

&lt;span class="c"&gt;# Finding hidden files and understanding permissions&lt;/span&gt;
&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-la&lt;/span&gt;
find &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f &lt;span class="nt"&gt;-name&lt;/span&gt; &lt;span class="s2"&gt;".*"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These aren't just command exercises – they simulate real scenarios where attackers hide malicious files or administrators need to locate specific system files.&lt;/p&gt;

&lt;h3&gt;
  
  
  Advanced Text Processing
&lt;/h3&gt;

&lt;p&gt;As you progress, Bandit introduces sophisticated text manipulation techniques essential for log analysis and data extraction:&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;# Pattern matching for specific data&lt;/span&gt;
&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"pattern"&lt;/span&gt; large_file.txt

&lt;span class="c"&gt;# Sorting and finding unique entries&lt;/span&gt;
&lt;span class="nb"&gt;sort &lt;/span&gt;data.txt | &lt;span class="nb"&gt;uniq&lt;/span&gt; &lt;span class="nt"&gt;-c&lt;/span&gt;

&lt;span class="c"&gt;# Working with binary data&lt;/span&gt;
strings binary_file | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="s2"&gt;"readable_text"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Cryptography in Practice
&lt;/h3&gt;

&lt;p&gt;Rather than abstract mathematical concepts, Bandit presents cryptography through practical applications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Base64 encoding/decoding&lt;/strong&gt; for data transmission&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;ROT13 cipher&lt;/strong&gt; for basic obfuscation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;File compression and decompression&lt;/strong&gt; chains&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SSL/TLS communication&lt;/strong&gt; with live services&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Network Security Fundamentals
&lt;/h3&gt;

&lt;p&gt;Advanced levels introduce network concepts that security professionals use daily:&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;# Port scanning and service identification&lt;/span&gt;
nmap &lt;span class="nt"&gt;-p&lt;/span&gt; port_range target

&lt;span class="c"&gt;# Network communication&lt;/span&gt;
nc &lt;span class="o"&gt;(&lt;/span&gt;netcat&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for &lt;/span&gt;various protocols

&lt;span class="c"&gt;# SSL/TLS client connections&lt;/span&gt;
openssl s_client &lt;span class="nt"&gt;-connect&lt;/span&gt; host:port
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Learning Psychology Behind Gamification
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Immediate Feedback Loop
&lt;/h3&gt;

&lt;p&gt;Each level provides instant validation. Success grants access to the next challenge, while failure requires problem-solving and research. This tight feedback loop maintains engagement and builds confidence progressively.&lt;/p&gt;

&lt;h3&gt;
  
  
  Problem-Based Learning
&lt;/h3&gt;

&lt;p&gt;Instead of memorizing commands, you're solving authentic problems. Need to find a file with specific properties? You'll learn &lt;code&gt;find&lt;/code&gt; command naturally. Need to analyze network traffic? &lt;code&gt;netstat&lt;/code&gt; and &lt;code&gt;nmap&lt;/code&gt; become tools, not just commands to memorize.&lt;/p&gt;

&lt;h3&gt;
  
  
  Community and Collaboration
&lt;/h3&gt;

&lt;p&gt;The OverTheWire &lt;a href="https://discord.gg/CPDYM3G" rel="noopener noreferrer"&gt;community&lt;/a&gt; fosters collaborative learning. Forums, IRC channels, and documentation encourage discussion without spoiling solutions, creating an environment where learners help each other grow.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-World Applications
&lt;/h2&gt;

&lt;h3&gt;
  
  
  For Aspiring Security Professionals
&lt;/h3&gt;

&lt;p&gt;Bandit provides foundational skills for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Penetration testing&lt;/strong&gt; methodologies&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Incident response&lt;/strong&gt; procedures&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Digital forensics&lt;/strong&gt; investigations&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security operations center (SOC)&lt;/strong&gt; work&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  For System Administrators
&lt;/h3&gt;

&lt;p&gt;The challenges develop crucial administrative skills:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Log analysis&lt;/strong&gt; and troubleshooting&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Service configuration&lt;/strong&gt; and management&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automation scripting&lt;/strong&gt; capabilities&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Security hardening&lt;/strong&gt; techniques&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  For Developers
&lt;/h3&gt;

&lt;p&gt;Modern development requires security awareness:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Secure coding&lt;/strong&gt; practices&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;DevSecOps&lt;/strong&gt; integration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Container security&lt;/strong&gt; fundamentals&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Infrastructure as code&lt;/strong&gt; security&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Advanced Concepts and Progression
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Privilege Escalation
&lt;/h3&gt;

&lt;p&gt;Later levels introduce sophisticated privilege escalation techniques, teaching:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SUID/SGID&lt;/strong&gt; binary exploitation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cron job&lt;/strong&gt; manipulation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Service account&lt;/strong&gt; abuse&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Environment variable&lt;/strong&gt; exploitation&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Version Control Security
&lt;/h3&gt;

&lt;p&gt;Git-based challenges demonstrate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Repository forensics&lt;/strong&gt; for sensitive data&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Branch and tag analysis&lt;/strong&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Commit history&lt;/strong&gt; investigation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Remote repository&lt;/strong&gt; interaction&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Building Your Learning Path
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Getting Started Strategy
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Set up your environment&lt;/strong&gt; with proper SSH clients and tools&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Document your progress&lt;/strong&gt; – maintain notes on techniques learned&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Join the community&lt;/strong&gt; for support and additional resources&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Practice consistently&lt;/strong&gt; – regular engagement builds muscle memory&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Advanced Techniques
&lt;/h3&gt;

&lt;p&gt;As you progress, develop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Automation scripts&lt;/strong&gt; for repetitive tasks&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Custom tools&lt;/strong&gt; for specific challenges&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Deep diving&lt;/strong&gt; into manual pages and documentation&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cross-referencing&lt;/strong&gt; with real-world security frameworks&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Technical Foundation for Cybersecurity Careers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Industry Relevance
&lt;/h3&gt;

&lt;p&gt;The skills developed through Bandit directly align with industry certifications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CompTIA Security+&lt;/strong&gt; foundational knowledge&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CEH (Certified Ethical Hacker)&lt;/strong&gt; practical skills&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;OSCP (Offensive Security Certified Professional)&lt;/strong&gt; methodology&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;CISSP&lt;/strong&gt; security management concepts&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Portfolio Development
&lt;/h3&gt;

&lt;p&gt;Completing Bandit demonstrates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Self-directed learning&lt;/strong&gt; capability&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Problem-solving&lt;/strong&gt; methodology&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Technical documentation&lt;/strong&gt; skills&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Persistence&lt;/strong&gt; and attention to detail&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion: More Than Just a Game
&lt;/h2&gt;

&lt;p&gt;OverTheWire Bandit represents a paradigm shift in technical education. By combining gamification with authentic security challenges, it creates an engaging learning environment that builds genuine, applicable skills.&lt;/p&gt;

&lt;p&gt;The platform's genius lies not just in its technical content, but in its understanding of how people learn best – through doing, failing, researching, and ultimately succeeding. Each level completed isn't just a game achievement; it's a step toward genuine cybersecurity competence.&lt;/p&gt;

&lt;p&gt;Whether you're starting your security journey or looking to sharpen existing skills, Bandit offers a structured, challenging, and ultimately rewarding path to technical mastery. The investment in time and effort pays dividends in real-world capability and career advancement.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Ready to begin your journey? The terminal is waiting, and the first challenge is just an SSH connection away.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://overthewire.org/wargames/bandit/bandit0.html" rel="noopener noreferrer"&gt;OverTheWire Bandit&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: Always approach learning platforms like OverTheWire with respect for their educational mission. Share knowledge and techniques, but avoid sharing specific solutions that might diminish the learning experience for others.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;I suggest having a notepad ready!&lt;/em&gt; Whether it’s a physical notebook or a digital document, tracking your commands, mistakes, and lessons learned helps reinforce your understanding and gives you something to reference later.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>programming</category>
      <category>linux</category>
      <category>beginners</category>
    </item>
    <item>
      <title>[Boost]</title>
      <dc:creator>Johnny Santamaria</dc:creator>
      <pubDate>Sat, 29 Mar 2025 05:28:39 +0000</pubDate>
      <link>https://dev.to/jds64/-3pl0</link>
      <guid>https://dev.to/jds64/-3pl0</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/devteam/join-our-first-ever-wecoded-challenge-celebrating-underrepresented-voices-in-tech-through-stories-5m5" class="crayons-story__hidden-navigation-link"&gt;Join Our First-Ever WeCoded Challenge – Celebrating Underrepresented Voices in Tech Through Stories &amp;amp; Code!&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;
          &lt;a class="crayons-logo crayons-logo--l" href="/devteam"&gt;
            &lt;img alt="The DEV Team logo" src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Forganization%2Fprofile_image%2F1%2Fd908a186-5651-4a5a-9f76-15200bc6801f.jpg" class="crayons-logo__image" width="800" height="800"&gt;
          &lt;/a&gt;

          &lt;a href="/jess" class="crayons-avatar  crayons-avatar--s absolute -right-2 -bottom-2 border-solid border-2 border-base-inverted  "&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F264%2Fb75f6edf-df7b-406e-a56b-43facafb352c.jpg" alt="jess profile" class="crayons-avatar__image" width="400" height="400"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/jess" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Jess Lee
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Jess Lee
                &lt;a href="/++"&gt;&lt;img alt="Subscriber" class="subscription-icon" src="https://assets.dev.to/assets/subscription-icon-805dfa7ac7dd660f07ed8d654877270825b07a92a03841aa99a1093bd00431b2.png" width="166" height="102"&gt;&lt;/a&gt;
                &lt;img alt="Community Curator" class="community-leader-icon" src="https://assets.dev.to/assets/community-leader-icon-b72c9e74eff54916e5c46c962f47ba40c9f611a71f8b157511f9613f69c0001b.svg" width="20" height="20"&gt;
              
              &lt;div id="story-author-preview-content-2309869" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/jess" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F264%2Fb75f6edf-df7b-406e-a56b-43facafb352c.jpg" class="crayons-avatar__image" alt="" width="400" height="400"&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Jess Lee&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

            &lt;span&gt;
              &lt;span class="crayons-story__tertiary fw-normal"&gt; for &lt;/span&gt;&lt;a href="/devteam" class="crayons-story__secondary fw-medium"&gt;The DEV Team&lt;/a&gt;
            &lt;/span&gt;
          &lt;/div&gt;
          &lt;a href="https://dev.to/devteam/join-our-first-ever-wecoded-challenge-celebrating-underrepresented-voices-in-tech-through-stories-5m5" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;Mar 5 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/devteam/join-our-first-ever-wecoded-challenge-celebrating-underrepresented-voices-in-tech-through-stories-5m5" id="article-link-2309869"&gt;
          Join Our First-Ever WeCoded Challenge – Celebrating Underrepresented Voices in Tech Through Stories &amp;amp; Code!
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/devchallenge"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;devchallenge&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/wecoded"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;wecoded&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/dei"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;dei&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/career"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;career&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/devteam/join-our-first-ever-wecoded-challenge-celebrating-underrepresented-voices-in-tech-through-stories-5m5" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/fire-f60e7a582391810302117f987b22a8ef04a2fe0df7e3258a5f49332df1cec71e.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/raised-hands-74b2099fd66a39f2d7eed9305ee0f4553df0eb7b4f11b01b6b1b499973048fe5.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="24" height="24"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;165&lt;span class="hidden s:inline"&gt;&amp;nbsp;reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/devteam/join-our-first-ever-wecoded-challenge-celebrating-underrepresented-voices-in-tech-through-stories-5m5#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              

              29&lt;span class="hidden s:inline"&gt;&amp;nbsp;comments&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            4 min read
          &lt;/small&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>devchallenge</category>
      <category>wecoded</category>
      <category>dei</category>
      <category>career</category>
    </item>
  </channel>
</rss>
