<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://b13rg.icecdn.tech//feed.xml" rel="self" type="application/atom+xml" /><link href="https://b13rg.icecdn.tech//" rel="alternate" type="text/html" /><updated>2025-10-30T01:08:57+00:00</updated><id>https://b13rg.icecdn.tech//feed.xml</id><title type="html">B’s Website</title><subtitle>Stuff about old code, vintage computers, and anything else</subtitle><author><name>Brooke Byers</name><email>ByersBr@pm.me</email></author><entry><title type="html">Properties and Patterns of Elegant Systems</title><link href="https://b13rg.icecdn.tech//algo-props/" rel="alternate" type="text/html" title="Properties and Patterns of Elegant Systems" /><published>2025-10-14T00:00:00+00:00</published><updated>2025-10-14T00:00:00+00:00</updated><id>https://b13rg.icecdn.tech//algo-props</id><content type="html" xml:base="https://b13rg.icecdn.tech//algo-props/"><![CDATA[<p>Algorithms exist within a system, and usually process one or more inputs to produce an output.
These principles can be applied across all levels of computing abstraction, from hardware and software to cloud and .
This post explores foundational patterns in system design that ensure algorithms and architectures are efficient, predictable, and scalable.</p>

<hr />

<blockquote>
  <p>[<a href="https://www.japandict.com/%E6%B5%81%E3%82%8C#entry-1552130">流れ</a>]</p>

  <p>Data flows through space</p>

  <p>Systems revel in balance</p>

  <p>The enmeshed gates sing</p>
</blockquote>

<hr />

<ul>
  <li><a href="#core-principles-of-system-design">Core Principles of System Design</a>
    <ul>
      <li><a href="#simplicity-minimize-complexity">Simplicity: Minimize Complexity</a></li>
      <li><a href="#performance-balance-efficiency--resources">Performance: Balance Efficiency \&amp; Resources</a></li>
      <li><a href="#predictability-consistent-behavior-over-time">Predictability: Consistent Behavior Over Time</a></li>
      <li><a href="#correctness-accuracy--precision">Correctness: Accuracy \&amp; Precision</a></li>
    </ul>
  </li>
  <li><a href="#data-handling-strategies">Data Handling Strategies</a>
    <ul>
      <li><a href="#data-locality-and-movement">Data Locality and Movement</a></li>
      <li><a href="#data-shapes-and-structure">Data Shapes and Structure</a></li>
      <li><a href="#pre-processing-for-efficiency">Pre-Processing for Efficiency</a></li>
      <li><a href="#composable-across-systems">Composable Across systems</a></li>
      <li><a href="#data-hierarchy-and-latency-reduction-scalability">Data Hierarchy and Latency Reduction Scalability</a></li>
    </ul>
  </li>
  <li><a href="#scalability--performance-optimization">Scalability \&amp; Performance Optimization</a>
    <ul>
      <li><a href="#system-bottlenecks-and-design-avoidance">System Bottlenecks and Design Avoidance</a></li>
      <li><a href="#cost-awareness-balancing-tradeoffs">Cost Awareness: Balancing Tradeoffs</a></li>
    </ul>
  </li>
  <li><a href="#adaptability-and-usability">Adaptability and Usability</a>
    <ul>
      <li><a href="#layers-of-levers-configuration">Layers of Levers: Configuration</a></li>
      <li><a href="#graceful-failure-handling-errors">Graceful Failure: Handling Errors</a></li>
      <li><a href="#documentation--user-experience">Documentation \&amp; User Experience</a></li>
    </ul>
  </li>
  <li><a href="#conclusion-building-elegant-systems">Conclusion: Building Elegant Systems</a></li>
  <li><a href="#appendix-i---case-studies--practical-examples">Appendix I - Case Studies \&amp; Practical Examples</a>
    <ul>
      <li><a href="#factorios-pathfinding-algorithm">Factorio’s Pathfinding Algorithm</a></li>
      <li><a href="#zip-64-and-tape-storage-limitations">Zip-64 and Tape Storage Limitations</a></li>
      <li><a href="#rainbow-tables-in-cryptoanalysis">Rainbow Tables in Cryptoanalysis</a></li>
    </ul>
  </li>
  <li><a href="#appendix-ii---links-to-more">Appendix II - Links to More</a>
    <ul>
      <li><a href="#core-principles">Core Principles</a></li>
      <li><a href="#data-handling">Data Handling</a></li>
      <li><a href="#adaptability-and-usability-1">Adaptability and Usability</a></li>
    </ul>
  </li>
</ul>

<h2 id="core-principles-of-system-design">Core Principles of System Design</h2>

<h3 id="simplicity-minimize-complexity">Simplicity: Minimize Complexity</h3>

<p>Avoid unnecessary features, but balance the minimalism with practicality.
Systems should have a narrow scope of problems to solve.
Feature and scope creep pull attention away from the central purpose of a system and introduce additional “error-surface” for bugs and issues to grow from.</p>

<p>Minimize the amount of stateful components in the system.
<a href="https://www.seangoedecke.com/good-system-design/">If you’re storing any kind of information for any amount of time, you have a lot of tricky decisions to make about how you save, store and serve it.</a>
Stateless systems are simpler to test and verify.
By pushing stateful to the edges of the system, it removes complexity from</p>

<p>The design should minimize edge cases in applied logic.
This makes it easier to mentally model the system and reason about functionality.</p>

<h3 id="performance-balance-efficiency--resources">Performance: Balance Efficiency &amp; Resources</h3>

<p>Adding more resources improves performance of a system, to a point.
The various limits of hardware and software all scale differently from each other, so performance characteristics end up differing across scales of operation.
Parallelism, pipelining, and serialization are common design patterns to spread work across resources.
The scaling structure used should be derived from the structure and behaviors of the input data.</p>

<p>While compute and memory are usually the primary focus, all aspects of the system should be considered.
Financial, network, disk, data speed/latency and power usage are all considerations in improving efficiency.</p>

<p>For best performance the system should exist in a balance of input and processing resources.
There should exist slack in the system, relational to the volume and processing cost of the system input.
If resources are saturated and there is a surge in input, there will be an greater impact to performance than if there are unused resources able to accommodate the additional load.
In my experience across computing applications 80-85% utilization is around the sweet spot.</p>

<h3 id="predictability-consistent-behavior-over-time">Predictability: Consistent Behavior Over Time</h3>

<p>Best / Average / Worst performance of the algorithm should be similar over the range of possible inputs.
To illustrate, sorting algorithms are simple to reason about.</p>

<p>Quick sort first partitions the array and then make two recursive calls.
Merge sort first makes recursive calls for the two halves, and then merges the two sorted halves.</p>

<table>
  <thead>
    <tr>
      <th>Algorithm</th>
      <th>Quicksort</th>
      <th>Merge Sort</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Time Complexity (Best)</td>
      <td>O(n log n)</td>
      <td>O(n log n)</td>
    </tr>
    <tr>
      <td>Time Complexity (Average)</td>
      <td>O(n log n)</td>
      <td>O(n log n)</td>
    </tr>
    <tr>
      <td>Time Complexity (Worst)</td>
      <td>O(n²)</td>
      <td>O(n log n)</td>
    </tr>
    <tr>
      <td>Space Complexity (Best)</td>
      <td>O(log n)</td>
      <td>O(n)</td>
    </tr>
    <tr>
      <td>Space Complexity (Average)</td>
      <td>O(log n)</td>
      <td>O(n)</td>
    </tr>
    <tr>
      <td>Space Complexity (Worst)</td>
      <td>O(n)</td>
      <td>O(n)</td>
    </tr>
  </tbody>
</table>

<p>They are roughly comparable in the best and average case, there is a drastic difference on worst case performance.
Algorithms may also perform differently on different scales of input data.</p>

<p>Despite this, a program could utilize both algorithms by detecting properties of the data before processing and using the right algorithm for the job.</p>

<p>While processing the data, resource usage should be stable.
There shouldn’t be choppy CPU usage, and 
Expensive operations like memory allocation and disk/network requests should be performed up front.
They should make data available to the core algorithm without hoarding CPU.</p>

<h3 id="correctness-accuracy--precision">Correctness: Accuracy &amp; Precision</h3>

<p>Algorithms should produce accurate results under all valid inputs.
They should adhere to specific constraints such as idempotency.
For simple problems math, a calculator would be useless if it performed carries on a portion of the time.
Problems modeled by systems need to have measurable outcomes.</p>

<p>Outputs derived from data should be factually correct (see LLMs)
Processing the same set of data twice should result in the same output.</p>

<p>In sorting algorithms, there is the concept of stable vs unstable sorting algorithms.
A stable algorithm will preserve the relative order of <strong>equal elements</strong>, while an unstable one may <em>or may not</em> shuffle the order of equal elements.
While this may not matter for sorting raw values, it can be valuable when sorting data structures on a field.
More entropy is introduced into the system but it is up to the goals of the system to determine if it’s relevant or not.</p>

<p>A concept from physical sciences is systematic error vs. random error.
Systematic error is a consistent, reproducible error that stem from within the system.
This error can be:</p>

<ul>
  <li>Clock drift</li>
  <li>Floating-point arithmetic</li>
  <li>Resource contention</li>
  <li>Outright error (2+2=5)</li>
  <li>Unchecked expiration</li>
</ul>

<p>Random error causes include instrument limitations, minor variations in measuring techniques, and environmental factors.</p>

<ul>
  <li>Input issues/fidelity</li>
  <li>Security</li>
  <li>Upstream outages</li>
  <li>Weather events</li>
  <li>Interstellar rays</li>
  <li>System just didn’t feel like it</li>
</ul>

<p>Systematic errors can mostly be solved.
Because they arise from the architecture and design of the system they can more directly be confronted.
Random errors can be mitigated but not eradicated.
The system exists within reality, but steps can be taken to minimize and compartmentalize the error.
“Swiss-hole” security allows individual layers to have failures/issues that can be covered by other layers.
Of course, if the holes line up you’re gonna have a bad time.</p>

<p>LLMs take an interesting approach by often being incorrect <em>and</em> confident.
From a high level, they are deterministic.
Provided the same seed and a 0 temperature, any model <em>should</em> provide
What is not deterministic is the system operating the LLM.
Differences in hardware, scheduling, and parallelism can shuffle the order of operations in a non-deterministic (from our perspective) way.</p>

<h2 id="data-handling-strategies">Data Handling Strategies</h2>

<h3 id="data-locality-and-movement">Data Locality and Movement</h3>

<p>Algorithm should be able to perform tasks on a subset of the data.
It should not require processing all the data to get a result (processing everything required for the result).</p>

<p>Data should be stored in a way that’s conducive to how it will be processed.
The algorithms data access patterns should integrate cleanly with how the data stored.
Usually, algorithms fall into either being message or stream oriented.
Data stored with random access make it easier to batch and compartmentalize work.</p>

<p>Algorithms that require random data access do <em>NOT</em> perform well with stream-oriented data sets</p>

<p>Memory access patterns should be optimized to minimize cache misses and maximize reuse of data in memory.
Individual, discrete units of data should be processed as few times as possible, extracting what’s needed for the lifetime of the algorithm in minimal passes.</p>

<p>At a programming level, this could mean utilizing hash maps and dicts to group data instead of using un-indexed lists.
Data storage-wise, this could mean s3 buckets with data spread across a folder tree that groups files on some valuable property.</p>

<h3 id="data-shapes-and-structure">Data Shapes and Structure</h3>

<p>What data shapes and structures are consumed and produced</p>

<p>Data structures describe how data, metadata, and references are contained.</p>

<p>The organization of data to play to strengths of algorithm.
Don’t want to over-index, or chop up data into too-fine pieces.
Analogy to min-maxing surface area and volume of a square.
Indexing shouldn’t be overly specific.
Meaningful clustering of data.</p>

<p>Graph traversal is a core computing problem where storage structure can drastically reduce work.
With just a raw list of nodes and edges, it can be expensive to determine paths between two nodes.</p>

<p>By storing the graph in a hash map, nodes can readily be hopped between.
The data will be able to be explored as a graph by hash lookups rather than iterating through a list.</p>

<p>Having the raw data organized in a map is nice, but additional information about the data can be gathered to further improve task performance.
“Regularization” of the input data can help simplify the problem you’re trying to solve.</p>

<p>Structure decisions control the way in which data is processed and interpreted.</p>

<h3 id="pre-processing-for-efficiency">Pre-Processing for Efficiency</h3>

<p>https://en.wikipedia.org/wiki/Predictor%E2%80%93corrector_method</p>

<p>Pre-processing data before it enters parts of the subsystem.
Key information about the data can be extracted before the primary processing takes place to better inform and optimize the processing.</p>

<p>For example, the speed of a search algorithm on a list will vary wildly depending on if the list is sorted or not.
Over an unsorted list, an algorithm would need to analyze each list item on it’s own.
By sorting the list beforehand, the algorithm can rely on guarantees inherent in the data being processed and utilize better algorithms.</p>

<p>The pre-processing can take place during or outside of the algorithm’s execution lifecycle.
Lookup tables are a common example of using pre-computing to improve task performance.
Before computers there were used to speed up calculation of complex functions in trigonometry, logarithms, statistics and more.</p>

<h3 id="composable-across-systems">Composable Across systems</h3>

<p>Instead of solving every problem, it is better to inter-operate with other tools.
The algorithm and tool that encases it should readily integrate with other applications and systems.</p>

<p>In the *nix world, most terminal applications are line-of-text oriented.
By following the “Unix Philosophy” of managing data as files and text streams, tools like <code class="language-plaintext highlighter-rouge">awk</code>, <code class="language-plaintext highlighter-rouge">grep</code>, <code class="language-plaintext highlighter-rouge">cat</code>, and <code class="language-plaintext highlighter-rouge">find</code> can be combined to solve a task.</p>

<h3 id="data-hierarchy-and-latency-reduction-scalability">Data Hierarchy and Latency Reduction Scalability</h3>

<p>Performance should be maintained across orders of magnitude of input.
The varying data sizes should be able to be handled with sacrificing efficiency (linear vs. quadratic growth).</p>

<p>Algorithms should have a clear model for describing how data moves through the system.
It should also be aware of the transport of data as it passes through caches, buffers, networks, and physical locations.</p>

<p>Compute should be placed near the input data.
Data should be processed near where it is stored to minimize latency side-effects.</p>

<p>In a cloud architecture settings, this looks like cross-AZ and cross-Region data movement.
In machine code this could be optimizing data slices for L1 and L2 processor cache.
Each layer from the processor doing the work is a magnitude of difference</p>

<p>The more copies of the data that exist makes it easier for them to become out of sync.
Cache layers can solve problems, but also create potential for many more.
Data Lifetimes</p>

<h2 id="scalability--performance-optimization">Scalability &amp; Performance Optimization</h2>

<h3 id="system-bottlenecks-and-design-avoidance">System Bottlenecks and Design Avoidance</h3>

<p>Choosing the right abstraction layer to solve problems.</p>

<p>Avoid system limitations through thoughtful design.</p>

<h3 id="cost-awareness-balancing-tradeoffs">Cost Awareness: Balancing Tradeoffs</h3>

<p>Computing costs money.
Every layer of abstraction has it’s own costs and benefits.</p>

<p>Our capitalist system uses cost as a driver, the financial aspects are also a driver in resource decisions.
With the rise of [SaaS, PaaS, *aaS] it can often be cheaper to outsource functions to achieve “good-enough” functionality in a short timeframe.</p>

<p>The system should optimize for cost per operation within itself.
Of course, “cost” needs to be defined by the hopefully benevolent entity managing the system.
Time is a large factor, not just of the operations performed but the time-effort cost of constructing, managing and deconstructing the system over it’s lifetime.</p>

<h2 id="adaptability-and-usability">Adaptability and Usability</h2>

<p>Able to easily adjust to changing inputs and environments.</p>

<p>This doesn’t need to happen “on the fly”, but the code describing the algorithm should be contained enough to be easily applied to other problems.</p>

<p>Many advances in science and engineering have been made by applying an old tool to a new problem.
By</p>

<h3 id="layers-of-levers-configuration">Layers of Levers: Configuration</h3>

<p>Configuration is an additional way of communicating intent to the system separate from the input data.</p>

<p>grouped by function and breadth of changes.
Is meant to abstract away complexities from the user, providing an ergonomic way to “steer the ship”
Keep it simple and self-explanatory.</p>

<p>Minimize side affects, provide clear</p>

<h3 id="graceful-failure-handling-errors">Graceful Failure: Handling Errors</h3>

<p>Errors will always happen.
Even if all inputs, states and outputs are accounted for, there are factors outside of the system.
Links go down, hardware fails, and cosmic rays love flipping bits.
When encountering unrecoverable issues, plan for a graceful landing and exit instead of plowing a burning path of faults, errors, and corruptions.</p>

<p>It is often better to fail out early rather then trying to continue and create bigger issues.
In databases, design patterns like transactions and Write-Ahead-Logging (WAL) help minimize erroneous writes from failing clients and servers.
By taking steps to preserve state before work is done the system can guarantee (to a degree) the atomicity of actions taken.</p>

<p>User-facing errors should be actionable and explain what went wrong.
They should communicate what the system was doing and what input was being operated on.
The message should cover the “who what when where” parts of the problem, the “why” and “how” are usually left to the user to solve.</p>

<p>Error codes and the like are valuable as an index, but they require external resources to index against.
Most systems aren’t constrained by bits and bytes so messages can be more verbose.
In programming I prefer to return errors instead of exception-ing out of the main control flow.
Exceptions are expensive, and forces core parts of the system to be ready to “play catch” from potentially disparate sub-systems.</p>

<h3 id="documentation--user-experience">Documentation &amp; User Experience</h3>

<p>Algorithms do not operate in a vacuum.
They must be cared for and maintained like any living thing.
Documentation should outline the use case for the algorithm and how to apply it.</p>

<p>The complexities of the system should be described in an understandable way.
It should use ideas and references familiar with the target audience.
Having a “system vocabulary” of concepts and modules gives users a scaffold to construct understanding upon.</p>

<p>There should also information on how the system fails.
Users should be able to easily determine the cause of errors, and have the tools/knowledge available to solve it themselves.</p>

<p>Even if it is the most optimal algorithm ever constructed for a task, if it is not ergonomic to users than it will be forked or forgotten.</p>

<h2 id="conclusion-building-elegant-systems">Conclusion: Building Elegant Systems</h2>

<p>Everything old is new again.
The questions of how to design a computing system have been asked and answered innumerable ways over the decades.
Most of the problems we strive to solve are echoes of those faced by our computing progenitors.
We as engineers face physical and digital constraints that design is bound to.</p>

<p>“Be vicariously lazy.” - <a href="https://archive.org/details/programmingperl000wall/page/374/">Programming Perl 1991</a></p>

<hr />

<h2 id="appendix-i---case-studies--practical-examples">Appendix I - Case Studies &amp; Practical Examples</h2>

<h3 id="factorios-pathfinding-algorithm">Factorio’s Pathfinding Algorithm</h3>

<p><a href="https://factorio.com/blog/post/fff-317">Factorio Friday Facts #317 - New pathfinding algorithm</a> describes their process in developing an npc algorithm.</p>

<p>In path-finding algorithms, this can mean grouping nodes by location or travel cost.
Group 1x1 locations into say 64x64 tiles.
the algorithm can then first process the large tiles to get a rough estimate of a path, then explore more likely tiles before less likely ones resulting in less data processing overall.</p>

<h3 id="zip-64-and-tape-storage-limitations">Zip-64 and Tape Storage Limitations</h3>

<p><img src="/images/algo-props/zip-64-layout.png" alt="ZIP-64 Internal Layout" />
<a href="https://en.wikipedia.org/wiki/ZIP_(file_format)#/media/File:ZIP-64_Internal_Layout.svg">Original</a></p>

<p>The <a href="https://en.wikipedia.org/wiki/ZIP_(file_format)#Structure">ZIP-64</a> internal layout places the index of stored files or “General Directory” at the end of the archive structure.
This is advantageous for <em>writing</em> a zip file, as the client only knows the data of the General Directory once all files are written.
This also can ease adding files, as they can overwrite the old General directory before writing the updated one.</p>

<p>This structure is <em>not</em> helpful when attempting to read an archive.
A naive implementation could require processing the entire zip file to read the General Directory and provide it to the user.</p>

<p>Some systems can work around this, but others have hard constraints on how data can be accessed.
High-density tape drives provide large amounts of storage, but only read data sequentially.
This makes <code class="language-plaintext highlighter-rouge">ZIP-64</code> particularly bad, as the system must first seek to the end of the tape before being able to meaningfully access an archive.</p>

<h3 id="rainbow-tables-in-cryptoanalysis">Rainbow Tables in Cryptoanalysis</h3>

<p><img src="/images/algo-props/rainbow-tables.png" alt="Rainbow Table illustration presented at Crypto 2003" />
<a href="https://en.wikipedia.org/wiki/Rainbow_table#/media/File:Dr._Oechslin_Rainbow_Table_Crypto_2003_Illustration.png">Original</a></p>

<p><a href="https://en.wikipedia.org/wiki/Rainbow_table">Rainbow Tables</a> are pre-computed chains of password hashes.</p>

<p>are another, more modern form of lookup tables.
They help drastically improve hash cracking performance.
Instead of storing every single plaintext and hash, “chains” of hashes are created and only the ends are stored.
The target hash is repeatedly hashed using the same method to create the chains.
If the result matches one of the chain “ends”, the algorithm can then hash from the head of the chain to determine the input that generates the matching hash.</p>

<h2 id="appendix-ii---links-to-more">Appendix II - Links to More</h2>

<h3 id="core-principles">Core Principles</h3>

<h4 id="simplicity">Simplicity</h4>

<ul>
  <li><a href="https://web.archive.org/web/20220815032635/https://susam.net/maze/wall/simplicity-of-irc.html">Simplicity of IRC - Susam Pal</a></li>
  <li><a href="https://htmx.org/essays/htmx-sucks/">htmx sucks - Carson Gross</a></li>
  <li><a href="https://www.seangoedecke.com/great-software-design/">Great software design looks underwhelming - Sean Goedecke</a></li>
  <li><a href="https://jack-vanlightly.com/blog/2023/11/14/the-architecture-of-serverless-data-systems">The Architecture of Serverless Data Systems - Jack Vanlightly</a></li>
</ul>

<h4 id="performance">Performance</h4>

<ul>
  <li><a href="https://arxiv.org/abs/2502.17779">Simulating Time with Square-Root Space - Ryan Williams</a></li>
  <li><a href="https://research.meekolab.com/exploring-the-power-of-parallelized-cpu-architectures">Exploring the Power of Parallelized CPU architectures - meekochii</a></li>
</ul>

<h4 id="predicatability">Predicatability</h4>

<ul>
  <li><a href="https://en.wikipedia.org/wiki/Best%2C_worst_and_average_case#Sorting_algorithms">Best-Worst-Avg.: Sorting Algorithms</a></li>
  <li><a href="https://www.geeksforgeeks.org/quick-sort-vs-merge-sort/">Quick Sort vs Merge Sort</a></li>
</ul>

<h4 id="correctness">Correctness</h4>

<ul>
  <li><a href="https://chandraji.dev/stable-and-unstable-sorting-why-stability-matters">Stable and Unstable Sorting: Why Stability Matters? - Siddharth Chandra</a></li>
</ul>

<h3 id="data-handling">Data Handling</h3>

<ul>
  <li><a href="https://web.archive.org/web/20230409151031/https://lasecwww.epfl.ch/pub/lasec/doc/Oech03.pdf">Make a Faster Cryptanalytic Time-Memory TradeOff - Philippe Oechslin</a></li>
  <li><a href="http://infolab.stanford.edu/~ullman/cs345notes/cs345-7.pdf">Hash-Based Improvements to A-Priori</a></li>
  <li><a href="https://cscheid.net/writing/data_science/regularization/index.html">Regularization - </a></li>
  <li><a href="https://reasonablypolymorphic.com/blog/solving-lcsa/">Using Obscure Graph Theory to solve PL Problems - Sandy Maguire</a></li>
  <li><a href="https://en.wikipedia.org/wiki/Hypergraph">Hypergraphs</a>, in the context of index and metadata creation.</li>
</ul>

<h3 id="adaptability-and-usability-1">Adaptability and Usability</h3>

<ul>
  <li><a href="https://github.com/hackergrrl/art-of-readme">The Art of README</a></li>
</ul>]]></content><author><name>Brooke Byers</name><email>ByersBr@pm.me</email></author><category term="[&quot;Programming&quot;]" /><summary type="html"><![CDATA[Algorithms exist within a system, and usually process one or more inputs to produce an output. This post explores foundational patterns in system design that ensure algorithms and architectures are efficient, predictable, and scalable.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://b13rg.icecdn.tech//images/algo-props/merville-garden-stanley-spencer.png" /><media:content medium="image" url="https://b13rg.icecdn.tech//images/algo-props/merville-garden-stanley-spencer.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Notes on the Croissant 1.0 Dataset Spec</title><link href="https://b13rg.icecdn.tech//croissant/" rel="alternate" type="text/html" title="Notes on the Croissant 1.0 Dataset Spec" /><published>2025-07-15T00:00:00+00:00</published><updated>2025-07-15T00:00:00+00:00</updated><id>https://b13rg.icecdn.tech//croissant</id><content type="html" xml:base="https://b13rg.icecdn.tech//croissant/"><![CDATA[<p>Spec: https://docs.mlcommons.org/croissant/docs/croissant-spec.html</p>

<p>Website: https://mlcommons.org/working-groups/data/croissant/</p>

<p>TRTL Lang Definition: <a href="https://github.com/mlcommons/croissant/blob/main/docs/croissant.ttl">github.com/mlcommons/croissant/docs/croissant.ttl</a></p>

<p>The Croissant spec is designed to provide a standard way to describe datasets properties, raw file relationships, and derived dataset attributes.</p>

<blockquote>
  <p>The Croissant metadata format simplifies how data is used by ML models.
It provides a vocabulary for dataset attributes, streamlining how data is loaded […]
Croissant enables the interchange of datasets between ML frameworks and beyond, tackling a variety of <strong>discoverability</strong>, <strong>portability &amp; reproducibility</strong>, and <strong>responsible AI</strong> challenges.</p>
</blockquote>

<p>For this post I mostly analyzed version 1.0</p>

<p>The Croissant format roughly consists of 3 parts:</p>

<table>
  <thead>
    <tr>
      <th>Type</th>
      <th>Desc.</th>
      <th>Objects</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>metadata</td>
      <td>information about the dataset, including description, source, licensing, and keywords</td>
      <td><code class="language-plaintext highlighter-rouge">Dataset</code></td>
    </tr>
    <tr>
      <td>resources</td>
      <td>information about the raw files/filesets contained in the dataset</td>
      <td><code class="language-plaintext highlighter-rouge">FileObject</code>, <code class="language-plaintext highlighter-rouge">FileSet</code>,</td>
    </tr>
    <tr>
      <td>structure+semantics</td>
      <td>data structure and resource processing guides</td>
      <td><code class="language-plaintext highlighter-rouge">RecordSet</code>, <code class="language-plaintext highlighter-rouge">Field</code>, <code class="language-plaintext highlighter-rouge">DataSource</code></td>
    </tr>
  </tbody>
</table>

<p><img src="/images/croissant/croissant-1-spec.png" alt="A simplified diagram of the Croissant spec" /></p>
<blockquote>
  <p>A simplified diagram of the Croissant spec</p>
</blockquote>

<h2 id="metadata">Metadata</h2>

<p>Builds on <a href="https://docs.mlcommons.org/croissant/docs/croissant-spec.html#schemaorgdataset">schema.org/Dataset</a>.</p>
<details>
  <summary>Croissant 1.0 Required Metadata Properties</summary>
  <div>

    <blockquote>
      <p><a href="https://docs.mlcommons.org/croissant/docs/croissant-spec.html#required">Source</a></p>
    </blockquote>

    <p>The following list of properties from schema.org must be specified for every Croissant dataset.</p>

    <table>
      <thead>
        <tr>
          <th>Property</th>
          <th>ExpectedType</th>
          <th>Cardinality</th>
          <th>Comments</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>@context</td>
          <td>URL</td>
          <td>ONE</td>
          <td>A set of JSON-LD context definitions that make the rest of the Croissant description less verbose. See the recommended JSON-LD context in Appendix 1.</td>
        </tr>
        <tr>
          <td>@type</td>
          <td>Text</td>
          <td>ONE</td>
          <td>The type of a Croissant dataset must be schema.org/Dataset.</td>
        </tr>
        <tr>
          <td>dct:conformsTo</td>
          <td>URL</td>
          <td>ONE</td>
          <td>Croissant datasets must declare that they conform to the versioned schema: http://mlcommons.org/croissant/1.0</td>
        </tr>
        <tr>
          <td>description</td>
          <td>Text</td>
          <td>ONE</td>
          <td>Description of the dataset.</td>
        </tr>
        <tr>
          <td>license</td>
          <td>CreativeWork, URL</td>
          <td>MANY</td>
          <td>The license of the dataset. Croissant recommends using the URL of a known license, e.g., one of the licenses listed at https://spdx.org/licenses/.</td>
        </tr>
        <tr>
          <td>name</td>
          <td>Text</td>
          <td>ONE</td>
          <td>The name of the dataset.</td>
        </tr>
        <tr>
          <td>url</td>
          <td>URL</td>
          <td>ONE</td>
          <td>The URL of the dataset. This generally corresponds to the Web page for the dataset.</td>
        </tr>
        <tr>
          <td>creator</td>
          <td>Organization, Person</td>
          <td>MANY</td>
          <td>The creator(s) of the dataset.</td>
        </tr>
        <tr>
          <td>datePublished</td>
          <td>Date, DateTime</td>
          <td>ONE</td>
          <td>The date the dataset was published.</td>
        </tr>
      </tbody>
    </table>

  </div>
</details>

<p>The metadata for the dataset is stored in the root of the Croissant file.
A URL is required to link to where the dataset can be found.
While only one can be set, there is also a <code class="language-plaintext highlighter-rouge">sameAs</code> field that allows pointing to matching datasets with different URLs (http, s3, ipfs, onion, etc).</p>

<p>A <code class="language-plaintext highlighter-rouge">keywords</code> field allows adding text and links about the dataset, perfect for tags or references.</p>

<p>Versioning and <code class="language-plaintext highlighter-rouge">isLiveDataset</code> attributes allow the dataset the change over time.
Changes to the dataset should be constrained to updating or creating, not deleting.
It is best utilized to add additional data and improve <code class="language-plaintext highlighter-rouge">RecordSet</code> information.
If the structure of the data is also changing it is probably better to create new, separate datasets.</p>

<h2 id="resources">Resources</h2>

<p>Resources refer to the actual files of the dataset.
Items are either a <code class="language-plaintext highlighter-rouge">FileObject</code> or a <code class="language-plaintext highlighter-rouge">FileSet</code>.
A <code class="language-plaintext highlighter-rouge">FileObject</code> is a single item, and <code class="language-plaintext highlighter-rouge">FileSet</code> is one or more items matching a glob pattern.</p>

<p>They are able to reference each other via the <code class="language-plaintext highlighter-rouge">containedIn</code> property.
This is useful for referencing compressed files like <code class="language-plaintext highlighter-rouge">.zip</code> and <code class="language-plaintext highlighter-rouge">.tar.gz</code>.
A <code class="language-plaintext highlighter-rouge">Fileset</code> entry and be created for the contents of a <code class="language-plaintext highlighter-rouge">.zip</code>, which is stored as a <code class="language-plaintext highlighter-rouge">FileObject</code>.
This allows for storing the dataset compressed with the Croissant file guiding individual file extraction.</p>

<h2 id="recordsets">RecordSets</h2>

<blockquote>
  <p>A RecordSet describes a set of structured records obtained from one or more data sources (typically a file or set of files) and the structure of these records, expressed as a set of fields (e.g., the columns of a table). A RecordSet can represent flat or nested data.</p>
</blockquote>

<p><code class="language-plaintext highlighter-rouge">RecordSets</code> do not reference resources directly.
Instead, they group <code class="language-plaintext highlighter-rouge">Field</code>s together which in turn reference resources.
This allows <code class="language-plaintext highlighter-rouge">RecordSets</code> to span multiple files, though that  definitely introduces additional complexity.</p>

<p>The <code class="language-plaintext highlighter-rouge">Field</code> object encodes information about dataset fields such as name, types, and extraction method.
They are able to reference other <code class="language-plaintext highlighter-rouge">Field</code>s and <code class="language-plaintext highlighter-rouge">RecordSet</code>s to represent complex data relationships.
Each field has a <code class="language-plaintext highlighter-rouge">source</code> property that references either a <code class="language-plaintext highlighter-rouge">DataSource</code> object or a URL.
<code class="language-plaintext highlighter-rouge">DataSource</code> link a <code class="language-plaintext highlighter-rouge">Field</code> to associated <code class="language-plaintext highlighter-rouge">RecordSet</code>s and <code class="language-plaintext highlighter-rouge">File*</code> resources, acting as a focal point describing what’s required to view a given <code class="language-plaintext highlighter-rouge">RecordSet</code>.</p>

<p>The <code class="language-plaintext highlighter-rouge">RecordSet</code>-related types are primarily designed for column data, but the referencing allows for describing more complex data structures.
An open-ended <code class="language-plaintext highlighter-rouge">data</code> field allows encoding data as JSON and reference it from <code class="language-plaintext highlighter-rouge">Field</code>s for properties like enums and categories.</p>

<h2 id="ecosystem-tools">Ecosystem Tools</h2>

<p>There is of course the <a href="https://github.com/mlcommons/croissant/tree/main">spec repository</a> which contains the code for:</p>

<ul>
  <li><a href="https://github.com/mlcommons/croissant/tree/main/python/mlcroissant">mlcroissant</a> Python library, with <a href="https://github.com/mlcommons/croissant/blob/main/python/mlcroissant/mlcroissant/scripts/validate.py">cli tool</a></li>
  <li><a href="https://github.com/mlcommons/croissant/tree/main/editor">web editor</a>, which is live at <a href="https://huggingface.co/spaces/MLCommons/croissant-editor">huggingface.co/spaces/MLCommons/croissant-editor</a> with login</li>
  <li>links to Croissant <a href="https://github.com/mlcommons/croissant#integrations">tools integrations</a></li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>Most of what I found focused on using Croissant files in data pipelines, not creating them.
Dataset management. tools and platforms integrate Croissant as plugins to the system instead of creating distinct Croissant tools.
A Croissant file is not the primary dataset management format for applications.
Instead, it is positioned as an interchange format for datasets.</p>

<p>Potential changes:</p>

<ul>
  <li>how to use with homomorphic encryption</li>
</ul>

<p>Planned changes: https://github.com/orgs/mlcommons/projects/44/views/1</p>

<ul>
  <li>Make spec encompass the lifetime of the dataset instead of a snapshot.</li>
  <li>defining and using custom “data-level annotations”</li>
  <li>Reference other  downstream/upstream datasets</li>
  <li>Add additional dataset structure vocabulary (n-dimensional arrays, geo-spacial, AV)</li>
</ul>]]></content><author><name>Brooke Byers</name><email>ByersBr@pm.me</email></author><category term="[&quot;Programming&quot;]" /><summary type="html"><![CDATA[The croissant spec is designed to provide a standard way to describe datasets properties, raw file relationships, and derived dataset attributes.]]></summary></entry><entry><title type="html">Applications vs. Systems</title><link href="https://b13rg.icecdn.tech//application-vs-system/" rel="alternate" type="text/html" title="Applications vs. Systems" /><published>2025-07-03T00:00:00+00:00</published><updated>2025-07-03T00:00:00+00:00</updated><id>https://b13rg.icecdn.tech//application-vs-system</id><content type="html" xml:base="https://b13rg.icecdn.tech//application-vs-system/"><![CDATA[<p>Applications and systems both describe aspects of modern “computer-ing”.
Application are usually user-facing programs designed to perform specific tasks while systems focus on the foundational infrastructure that enables applications to operate.
Protocols are the glue that allows applications and systems to communicate intent.</p>

<h2 id="applications---the-system-user-interface">Applications - The System-User Interface</h2>

<p>Programs designed for end-users
Focused on specific tasks or providing value to users.</p>

<p>Provide a varied interface to interact with systems.
Presentation guides user interaction and places constraints of what the user is able to create.</p>

<h3 id="key-properties-of-applications">Key Properties of Applications</h3>

<ul>
  <li>User-centric: Focuses on direct interaction with users through GUIs, APIs, CLIs, etc.</li>
  <li>Task-specific: Often aimed at solving a particular problem</li>
  <li>External Dependencies: Relies on systems such as web browsers, operating systems, databases, etc. to function.</li>
  <li>Modular: Often composed of small components such as plugins or libraries that can be updated or replaced.</li>
</ul>

<h2 id="systems---infrastructure-at-large">Systems - Infrastructure at large</h2>

<p>Underlying hardware and software that manages resources, executes code, and enables applications.</p>

<h3 id="key-properties-of-systems">Key Properties of Systems</h3>

<ul>
  <li>Resource management: Control processing, memory, disk, network</li>
  <li>Abstraction Layers: Provides APIs or interfaces to simplify interacting with other components</li>
  <li>Foundational: Create the base for applications to run</li>
  <li>Scalable: Designed to handle large-scale operations</li>
</ul>

<h2 id="protocols---calls-from-inside-the-stack">Protocols - Calls from Inside the Stack</h2>

<p>Rules governing how applications and systems interact with each other.
Ensure interoperability, security, and efficiency across networks.</p>

<p>Provide guarantees that applications and systems can rely to communicate information.
Span multiple layers of the hardware/software abstraction stack.</p>

<p>Open protocols are more valuable than proprietary ones.
Accessability and visibility is important to be able to verify protocol guarantees.</p>

<p>Security properties should be integrated with protocols as a principle.
Aspects like checksums provide authentication and integrity guarantees that applications and systems can trust and rely on.</p>

<h3 id="key-properties-of-protocols">Key properties of Protocols</h3>

<ul>
  <li>Standardization of structure and transport</li>
  <li>Security-focused: Define encryption, authentication, and data integrity mechanisms</li>
  <li>Adaptable: Able to evolve with technological advancements</li>
</ul>

<h2 id="interdependencies---applications-systems-and-protocols-together">Interdependencies - Applications, Systems and Protocols Together</h2>

<p>In a graph of computing, applications are leaves, systems are nodes and protocols are edges.
Application depend on systems to operate.
Systems rely on protocols to communicate with applications and other systems.
Protocols provide information communication guarantees, and enable applications to function and interact with diverse systems.</p>]]></content><author><name>Brooke Byers</name><email>ByersBr@pm.me</email></author><category term="[&quot;Programming&quot;]" /><summary type="html"><![CDATA[An exploration of applications, systems, and a secret third option]]></summary></entry><entry><title type="html">Re-Zeroing the Z Axis with BLTouch on Ender 5 Pro</title><link href="https://b13rg.icecdn.tech//adjusting-z-offset/" rel="alternate" type="text/html" title="Re-Zeroing the Z Axis with BLTouch on Ender 5 Pro" /><published>2025-06-14T00:00:00+00:00</published><updated>2025-06-14T00:00:00+00:00</updated><id>https://b13rg.icecdn.tech//adjusting-z-offset</id><content type="html" xml:base="https://b13rg.icecdn.tech//adjusting-z-offset/"><![CDATA[<p>Setup:</p>

<ul>
  <li>Ender 5 pro hardware w/ direct feed and non-default nozzle bracket</li>
  <li>BIGTREETECH SKR Mini E3 control board
    <ul>
      <li><strong>Marlin</strong>-based firmware; BIGTREETECH build: <a href="https://github.com/bigtreetech/BIGTREETECH-SKR-mini-E3/tree/master/firmware/V3.0/Marlin">firmware-ender5-bltouch.bin</a></li>
    </ul>
  </li>
  <li><a href="https://docs.octoprint.org/en/master/">Octopi</a> via Rasperberry Pi Zero W</li>
</ul>

<p>The new part: <a href="https://www.antclabs.com/bltouch-v3">BLTouch Probe</a></p>

<p>NOT wired for Z-homing.
Still uses switch triggered by the bed.</p>

<h2 id="re-zeroing-the-z-axis">Re-Zeroing the Z Axis</h2>

<p>G-Code lookup: https://marlinfw.org/meta/gcode/</p>

<p>From <a href="https://marlinfw.org/docs/gcode/M851.html">M851 - XYZ Probe Offset</a> docs:</p>

<blockquote>
  <p>… the easiest way to get the Z offset value (assuming your homed Z = <code class="language-plaintext highlighter-rouge">0.0</code>) is to:</p>

  <ul>
    <li>Home the Z axis.</li>
    <li>Raise Z and deploy the probe.</li>
    <li>Move Z down slowly until the probe triggers.</li>
    <li>Take the current Z position (as reported by <code class="language-plaintext highlighter-rouge">M119</code>) and negate it. (<code class="language-plaintext highlighter-rouge">5.2</code> =&gt; <code class="language-plaintext highlighter-rouge">-5.2</code>)</li>
    <li>Set with <code class="language-plaintext highlighter-rouge">M851 Z-5.2</code> and save it with <code class="language-plaintext highlighter-rouge">M500</code>.</li>
  </ul>
</blockquote>

<h3 id="get-current-settings-m503">Get Current settings: <a href="https://marlinfw.org/docs/gcode/M503.html"><strong>M503</strong></a></h3>

<details>
  <summary>Example M503 Output</summary>
  <div>

    <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">Send</span><span class="pi">:</span> <span class="s">M503</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">echo:; Linear Units</span><span class="err">:</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G21 ;</span>
<span class="na">Recv</span><span class="pi">:</span>  <span class="s">(mm)</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">echo:; Temperature Units</span><span class="err">:</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">M149 C ; Units in Celsius</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">echo:; Filament settings (Disabled)</span><span class="err">:</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">M200 S0 D1.75</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">echo:; Steps per unit</span><span class="err">:</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">M92 X80.00 Y80.00 Z800.00 E130.00</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">echo:; Max feedrates (units/s)</span><span class="err">:</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">M203 X500.00 Y500.00 Z5.00 E25.00</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">echo:; Max Acceleration (units/s2)</span><span class="err">:</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">M201 X500.00 Y500.00 Z100.00 E10000.00</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">echo:; Acceleration (units/s2) (P&lt;print-accel&gt; R&lt;retract-accel&gt; T&lt;travel-accel&gt;)</span><span class="err">:</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">M204 P500.00 R500.00 T500.00</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">echo:; Advanced (B&lt;min_segment_time_us&gt; S&lt;min_feedrate&gt; T&lt;min_travel_feedrate&gt; J&lt;junc_dev&gt;)</span><span class="err">:</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">M205 B20000.00 S0.00 T0.00 J0.01</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">echo:; Home offset</span><span class="err">:</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">M206 X0.00 Y0.00 Z0.00</span>
<span class="c1"># --- Bed leveling coordinate values</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">echo:; Auto Bed Leveling</span><span class="err">:</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">M420 S1 Z0.00 ; Leveling ON</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I0 J0 Z0.20875</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I1 J0 Z0.16500</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I2 J0 Z0.13000</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I3 J0 Z0.10375</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I4 J0 Z0.16625</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I0 J1 Z0.17000</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I1 J1 Z0.18375</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I2 J1 Z0.13500</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I3 J1 Z0.14750</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I4 J1 Z0.18375</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I0 J2 Z0.16000</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I1 J2 Z0.12000</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I2 J2 Z0.12750</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I3 J2 Z0.09875</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I4 J2 Z0.17500</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I0 J3 Z0.13750</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I1 J3 Z0.15250</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I2 J3 Z0.12875</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I3 J3 Z0.12000</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I4 J3 Z0.14250</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I0 J4 Z0.09125</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I1 J4 Z0.12125</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I2 J4 Z0.12000</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I3 J4 Z0.11000</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">G29 W I4 J4 Z0.20125</span>
<span class="c1"># ---</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">echo:; Material heatup parameters</span><span class="err">:</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">M145 S0 H185.00 B45.00 F255</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">M145 S1 H240.00 B110.00 F255</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">echo:; Hotend PID</span><span class="err">:</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">M301 P21.73 I1.54 D76.55</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">echo:; Bed PID</span><span class="err">:</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">M304 P41.78 I7.32 D158.93</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">echo:; Controller Fan</span><span class="err">:</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">M710 S255 I0 A1 D60 ; (100% 0%)</span>
<span class="c1"># --- Probe Offset from Nozzle </span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">echo:; Z-Probe Offset</span><span class="err">:</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">M851 X-40.00 Y-10.00 Z-3.85 ; (mm)</span>
<span class="c1"># ---</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">echo:; Stepper driver current</span><span class="err">:</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">M906 X580 Y580 Z580</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">M906 T0 E650</span>
<span class="na">Recv</span><span class="pi">:</span> 
<span class="na">Recv</span><span class="pi">:</span> <span class="s">echo:; Driver stepping mode</span><span class="err">:</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">M569 S1 X Y Z</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">M569 S1 T0 E</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">echo:; Linear Advance</span><span class="err">:</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">M900 K0.00</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">echo:; Filament load/unload</span><span class="err">:</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="na">echo</span><span class="pi">:</span>  <span class="s">M603 L350.00 U400.00 ; (mm)</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">ok</span>
</code></pre></div>    </div>
  </div>
</details>

<h3 id="adjust-z-probe-offset-m851">Adjust Z-Probe Offset: <a href="https://marlinfw.org/docs/gcode/M851.html"><strong>M851</strong></a></h3>

<details>
  <summary>Example M503 Output</summary>
  <div>

    <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">Send</span><span class="pi">:</span> <span class="s">M851 X-40.00 Y-10.00 Z-3.94</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">ok</span>
</code></pre></div>    </div>
  </div>
</details>

<h3 id="save-settings-m500">Save Settings: <a href="https://marlinfw.org/docs/gcode/M500.html"><strong>M500</strong></a></h3>

<details>
  <summary>Example M500 Output</summary>
  <div>

    <div class="language-yml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">Send</span><span class="pi">:</span> <span class="s">M500</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">echo:Settings Stored (685 bytes; crc 53261)</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">//action:notification Settings Stored</span>
<span class="na">Recv</span><span class="pi">:</span> <span class="s">ok</span>
</code></pre></div>    </div>

  </div>
</details>]]></content><author><name>Brooke Byers</name><email>ByersBr@pm.me</email></author><category term="[&quot;3DPrinting&quot;]" /><summary type="html"><![CDATA[This guide walks through re-zeroing the Z-axis using a BLTouch probe on an Ender 5 Pro with Marlin firmware.]]></summary></entry><entry><title type="html">Build Caching Systems</title><link href="https://b13rg.icecdn.tech//build-cache/" rel="alternate" type="text/html" title="Build Caching Systems" /><published>2025-05-23T00:00:00+00:00</published><updated>2025-05-23T00:00:00+00:00</updated><id>https://b13rg.icecdn.tech//build-cache</id><content type="html" xml:base="https://b13rg.icecdn.tech//build-cache/"><![CDATA[<p>While working on <a href="https://github.com/ice-bergtech/kr8">kr8+</a>, I wanted to add a build cache to speed up building large projects, especially when changes only affect a small portion of the codebase.</p>

<h2 id="docker-build-caching">Docker Build Caching</h2>

<p>One build cache system I was already aware of was Docker layers.
A docker image result and build cache can be represented as a singly linked list of layers.
Each layer is a dockerfile instruction that is dependent on the layers that come before it.
As part of the docker build process, each layer is cached.
If the same layer is already cached, it can be reused instead of recreated.</p>

<p>From the Duckduckgo:</p>

<ul>
  <li>Docker Docs: <a href="https://docs.docker.com/build/cache/">Build Cache</a></li>
  <li>Useful.codes: <a href="https://useful.codes/understanding-image-layers-and-caching-in-docker/">Understanding Image Layers and Caching in Docker</a></li>
</ul>

<p>Docker determines if a cached layer is valid by checking:</p>

<ul>
  <li>layer instruction: <code class="language-plaintext highlighter-rouge">FROM</code>, <code class="language-plaintext highlighter-rouge">RUN</code>, <code class="language-plaintext highlighter-rouge">COPY</code>, etc.</li>
  <li>layer build context</li>
  <li>layers that came before</li>
  <li>build arguments</li>
</ul>

<p>The layer build context documentation seemed rather vague, with the <a href="https://docs.docker.com/build/concepts/context/">Context docs</a> stating the context is made up of the files and directories sent to the builder.
I was curious</p>

<p>Based on the <a href="https://github.com/moby/docker-image-spec/blob/v1.2.0/v1.2.md">Docker Image Specification v1.2.0</a>.
Each layer is tracked based on it’s <code class="language-plaintext highlighter-rouge">DiffID</code>, then the layer diff IDs are stored in the image’s <code class="language-plaintext highlighter-rouge">rootfs</code> field, “in order from bottom-most to top-most”:</p>

<blockquote>
  <p>Layers are referenced by cryptographic hashes of their serialized representation.
This is a SHA256 digest over the tar archive used to transport the layer, represented as a hexadecimal encoding of 256 bits, e.g., sha256:a9561eb1b190625c9adb5a9513e72c4dedafc1cb2d4c5236c9a6957ec7dfd5a9.
Layers must be packed and unpacked reproducibly to avoid changing the layer ID, for example by using tar-split to save the tar headers.
Note that the digest used as the layer ID is taken over an uncompressed version of the tar.</p>
</blockquote>

<p>The Docker documentation references using <a href="https://github.com/docker/buildx">Buildx</a> as the default image building backend, which provides files and input to BuildKit which performs the actual image build.
The Docker docs <a href="https://docs.docker.com/build/concepts/overview/">Build Overview</a> and <a href="https://docs.docker.com/build/buildkit/">BuildKit</a> cover these topics from a high-level perspective.</p>

<p>I was curious what specific data was processed by Buildx and BuildKit to create the cache layers so I dug into them to see how it was implemented in practice.
After getting lost in the buildx code and not seeing any actual caching happening, I found that the actual caching operation was is performed by <a href="https://github.com/moby/buildkit">BuildKit</a>, separate from Buildx.
The <a href="https://blog.mobyproject.org/introducing-buildkit-17e056cc5317">Buildkit intro post</a> provides an overview of BuildKit’s methodologies early capabilities.</p>

<p>While being built, the image data is converted into a “build definition format” <a href="https://pkg.go.dev/github.com/moby/buildkit/client/llb">LLB</a> (low-level builder).
LLB allows defining complex build definitions through a content-addressable dependency DAG (directed, acyclic graph).
The protobuf definition is located at <a href="https://github.com/moby/buildkit/blob/master/solver/pb/ops.proto">buildkit/solver/pb/ops.proto</a>.
As the graph is being created, “branches” can be pruned based on cache validity.
To perform the build, the program simply walks the graph visiting all nodes.</p>

<details>
<summary>Getting sidetracked and lost in Buildx state structs</summary>
  <div>

    <p>At the “edge” of the docker build code, build context seems to be represented by the <code class="language-plaintext highlighter-rouge">Inputs</code> struct in <a href="https://github.com/docker/buildx/blob/542bda49f2b25f32fa424c3dc50f5b9b5c50d280/build/build.go#L106">buildx/build/build.go</a> as a set of paths, file info, and context states.</p>

    <p>The definition of a state from <a href="https://github.com/docker/buildx/blob/542bda49f2b25f32fa424c3dc50f5b9b5c50d280/localstate/localstate.go#L23">buildx/localstate/localstate.go</a>, and is created from the same <code class="language-plaintext highlighter-rouge">buildOpts</code> that are used to create the layer.</p>

    <div class="language-golang highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// https://github.com/docker/buildx/blob/542bda49f2b25f32fa424c3dc50f5b9b5c50d280/localstate/localstate.go#L23</span>
<span class="k">type</span> <span class="n">State</span> <span class="k">struct</span> <span class="p">{</span>
	<span class="c">// Target is the name of the invoked target (default if empty)</span>
	<span class="n">Target</span> <span class="kt">string</span>
	<span class="c">// LocalPath is the absolute path to the context or remote context</span>
	<span class="n">LocalPath</span> <span class="kt">string</span>
	<span class="c">// DockerfilePath is the absolute path to the Dockerfile or relative if</span>
	<span class="c">// context is remote</span>
	<span class="n">DockerfilePath</span> <span class="kt">string</span>
	<span class="c">// GroupRef is the ref of the state group that this ref belongs to</span>
	<span class="n">GroupRef</span> <span class="kt">string</span> <span class="s">`json:",omitempty"`</span>
<span class="p">}</span>

<span class="c">// Stored as GroupRef in State struct</span>
<span class="k">type</span> <span class="n">StateGroup</span> <span class="k">struct</span> <span class="p">{</span>
	<span class="c">// Targets are the targets invoked</span>
	<span class="n">Targets</span> <span class="p">[]</span><span class="kt">string</span> <span class="s">`json:",omitempty"`</span>
	<span class="c">// Refs are used to track all the refs that belong to the same group</span>
	<span class="n">Refs</span> <span class="p">[]</span><span class="kt">string</span>
<span class="p">}</span>
</code></pre></div>    </div>

    <p>The refs are first allocated during the “baking” process, as part of the <code class="language-plaintext highlighter-rouge">saveLocalStateGroup</code> function.
This takes place <em>before</em> the layer is built.
The function is passed the same parameters used to create the layer:</p>

    <div class="language-golang highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// https://github.com/docker/buildx/blob/542bda49f2b25f32fa424c3dc50f5b9b5c50d280/commands/bake.go#L526</span>
<span class="k">func</span> <span class="n">saveLocalStateGroup</span><span class="p">(</span><span class="n">dockerCli</span> <span class="n">command</span><span class="o">.</span><span class="n">Cli</span><span class="p">,</span> <span class="n">in</span> <span class="n">bakeOptions</span><span class="p">,</span> <span class="n">targets</span> <span class="p">[]</span><span class="kt">string</span><span class="p">,</span> <span class="n">buildOpts</span> <span class="k">map</span><span class="p">[</span><span class="kt">string</span><span class="p">]</span><span class="n">build</span><span class="o">.</span><span class="n">Options</span><span class="p">)</span> <span class="kt">error</span> <span class="p">{</span>
	<span class="n">l</span><span class="p">,</span> <span class="n">err</span> <span class="o">:=</span> <span class="n">localstate</span><span class="o">.</span><span class="n">New</span><span class="p">(</span><span class="n">confutil</span><span class="o">.</span><span class="n">NewConfig</span><span class="p">(</span><span class="n">dockerCli</span><span class="p">))</span>

  <span class="c">// ... checks + metadata provenance mode settings ...</span>

	<span class="n">groupRef</span> <span class="o">:=</span> <span class="n">identity</span><span class="o">.</span><span class="n">NewID</span><span class="p">()</span> <span class="c">// &lt;--------- state group identifier</span>
	<span class="n">refs</span> <span class="o">:=</span> <span class="nb">make</span><span class="p">([]</span><span class="kt">string</span><span class="p">,</span> <span class="m">0</span><span class="p">,</span> <span class="nb">len</span><span class="p">(</span><span class="n">buildOpts</span><span class="p">))</span>
	<span class="k">for</span> <span class="n">k</span><span class="p">,</span> <span class="n">b</span> <span class="o">:=</span> <span class="k">range</span> <span class="n">buildOpts</span> <span class="p">{</span>
		<span class="n">b</span><span class="o">.</span><span class="n">Ref</span> <span class="o">=</span> <span class="n">identity</span><span class="o">.</span><span class="n">NewID</span><span class="p">()</span>  <span class="c">// &lt;--------- state group item identifier</span>
		<span class="n">refs</span> <span class="o">=</span> <span class="nb">append</span><span class="p">(</span><span class="n">refs</span><span class="p">,</span> <span class="n">b</span><span class="o">.</span><span class="n">Ref</span><span class="p">)</span>
		<span class="c">// ... update build options ...</span>
	<span class="p">}</span>

  <span class="c">// ... checks ...</span>

	<span class="k">return</span> <span class="n">l</span><span class="o">.</span><span class="n">SaveGroup</span><span class="p">(</span><span class="n">groupRef</span><span class="p">,</span> <span class="n">localstate</span><span class="o">.</span><span class="n">StateGroup</span><span class="p">{</span>
		<span class="n">Refs</span><span class="o">:</span>    <span class="n">refs</span><span class="p">,</span>
		<span class="n">Targets</span><span class="o">:</span> <span class="n">targets</span><span class="p">,</span>
	<span class="p">})</span>
<span class="p">}</span>
</code></pre></div>    </div>

    <p>The cache entry is defined as a <code class="language-plaintext highlighter-rouge">localstate.StateGroup</code>, with each <code class="language-plaintext highlighter-rouge">buildOpt</code> used to create the layer stored as well.</p>

    <p>The identity is generated by <a href="https://github.com/moby/buildkit/blob/5d47024a21ec128c8caf7af2ab4b83d63d9a4094/identity/randomid.go#L43">moby/buildkit/identity</a>, which is  just a <code class="language-plaintext highlighter-rouge">big.Int</code> of random bytes: (with consts applied) <code class="language-plaintext highlighter-rouge">(&amp;big.Int{}).SetBytes(p[:]).Text(36)[1 : 25+1]</code>.</p>

    <p>A core piece of build (and cache context state) is the <code class="language-plaintext highlighter-rouge">buildOpts</code> var, which contains most of the inputs used to build the layer.
It is defined as an <code class="language-plaintext highlighter-rouge">Options</code> struct in <a href="https://github.com/docker/buildx/blob/542bda49f2b25f32fa424c3dc50f5b9b5c50d280/build/build.go#L62">buildx/build/build.go</a>.</p>

    <p>The <code class="language-plaintext highlighter-rouge">buildOpt</code>s for each layer is generated by <code class="language-plaintext highlighter-rouge">tobuildOpt()</code> in <a href="https://github.com/docker/buildx/blob/542bda49f2b25f32fa424c3dc50f5b9b5c50d280/bake/bake.go#L1298">buildx/bake/bake.go</a>.</p>

    <p>For each layer the <code class="language-plaintext highlighter-rouge">Options</code> struct used to configure the build is used to <a href="https://github.com/docker/buildx/blob/542bda49f2b25f32fa424c3dc50f5b9b5c50d280/bake/bake.go#L1298">construct a <code class="language-plaintext highlighter-rouge">BuildOpt</code></a>, which in turn is used to generate the cache entry.</p>

    <p>This is all for initializing the cache entry, which takes place before the build happens.
The cache ends up actually being created as part of the build managed by <a href="https://github.com/moby/buildkit">Buildkit</a>.</p>

  </div>
</details>

<h3 id="buildkitllb-file-hashing">BuildKit/LLB File Hashing</h3>

<p>Each layer of the docker image contains an instruction to execute.
A <a href="https://stackoverflow.com/questions/71887067/how-are-docker-buildx-layer-cache-hashes-calculated">Stack Overflow</a> answer pointed out different instructions have different cache considerations.
The aspect I was most interested in was discovering how file hashes were calculated.
Modifying a file used in a layer will invalidate the layer, but from experience I’ve found not all properties of a file are considered.
For example, after a <code class="language-plaintext highlighter-rouge">docker build</code> takes place, you can <code class="language-plaintext highlighter-rouge">touch</code> referenced files to update their timestamp without invalidating the layer cache.</p>

<p>Generating context entries for files and directories is performed in <a href="https://github.com/moby/buildkit/blob/5d47024a21ec128c8caf7af2ab4b83d63d9a4094/cache/contenthash/checksum.go#L868">buildkit/cache/contenthash/checksum.go</a> is used to calculate the file checksums.
<code class="language-plaintext highlighter-rouge">Checksum()</code> calls <a href="https://github.com/moby/buildkit/blob/5d47024a21ec128c8caf7af2ab4b83d63d9a4094/cache/contenthash/checksum.go#L1206"><code class="language-plaintext highlighter-rouge">prepareDigest(fp, p string, fi os.FileInfo)</code></a>.
Using file info derived from <code class="language-plaintext highlighter-rouge">os.LStat()</code>, it passes this to <a href="https://github.com/moby/buildkit/blob/5d47024a21ec128c8caf7af2ab4b83d63d9a4094/cache/contenthash/filehash.go#L16"><code class="language-plaintext highlighter-rouge">NewFileHash(path string, fi os.FileInfo) hash.Hash</code></a>.</p>

<p>This function copies parts of the <code class="language-plaintext highlighter-rouge">fi</code> param into a <code class="language-plaintext highlighter-rouge">fsutil.types.Stat</code> struct which in turn is placed into a <code class="language-plaintext highlighter-rouge">archive/tar</code> <a href="https://cs.opensource.google/go/go/+/refs/tags/go1.24.3:src/archive/tar/common.go;drc=bc7c35a6d3bb0074d07beebedc0afcbdcebb8d3f;l=147"><code class="language-plaintext highlighter-rouge">Header</code></a> struct by <a href="https://cs.opensource.google/go/go/+/refs/tags/go1.24.3:src/archive/tar/common.go;drc=bc7c35a6d3bb0074d07beebedc0afcbdcebb8d3f;l=647"><code class="language-plaintext highlighter-rouge">tar.FileInfoHeader()</code></a>.</p>

<p>An interesting aspect of this conversion (and why we can <code class="language-plaintext highlighter-rouge">touch</code> files) is that only a portion of the <code class="language-plaintext highlighter-rouge">Header</code> fields are set by BuildKit.
A hash is then generated from this header and a <a href="https://github.com/moby/buildkit/blob/5d47024a21ec128c8caf7af2ab4b83d63d9a4094/cache/contenthash/filehash.go#L79">tarsumHash struct</a> is returned.
The header that is hashed is generated by <a href="https://github.com/moby/buildkit/blob/5d47024a21ec128c8caf7af2ab4b83d63d9a4094/cache/contenthash/tarsum.go#L12"><code class="language-plaintext highlighter-rouge">WriteV1TarsumHeader()</code></a>, which generates a <code class="language-plaintext highlighter-rouge">v1TarHeader</code> from file <a href="https://www.gnu.org/software/tar/manual/html_node/Extended-File-Attributes.html">GNU tar Extended File Attributes</a> and <code class="language-plaintext highlighter-rouge">v0TarHeader</code> information:</p>

<div class="language-golang highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">// https://github.com/moby/buildkit/blob/5d47024a21ec128c8caf7af2ab4b83d63d9a4094/cache/contenthash/tarsum.go#L21C1-L36C2</span>
<span class="k">func</span> <span class="n">v0TarHeaderSelect</span><span class="p">(</span><span class="n">h</span> <span class="o">*</span><span class="n">tar</span><span class="o">.</span><span class="n">Header</span><span class="p">)</span> <span class="p">(</span><span class="n">orderedHeaders</span> <span class="p">[][</span><span class="m">2</span><span class="p">]</span><span class="kt">string</span><span class="p">)</span> <span class="p">{</span>
	<span class="k">return</span> <span class="p">[][</span><span class="m">2</span><span class="p">]</span><span class="kt">string</span><span class="p">{</span>
		<span class="p">{</span><span class="s">"name"</span><span class="p">,</span> <span class="n">h</span><span class="o">.</span><span class="n">Name</span><span class="p">},</span>
		<span class="p">{</span><span class="s">"mode"</span><span class="p">,</span> <span class="n">strconv</span><span class="o">.</span><span class="n">FormatInt</span><span class="p">(</span><span class="n">h</span><span class="o">.</span><span class="n">Mode</span><span class="p">,</span> <span class="m">10</span><span class="p">)},</span>
		<span class="p">{</span><span class="s">"uid"</span><span class="p">,</span> <span class="n">strconv</span><span class="o">.</span><span class="n">Itoa</span><span class="p">(</span><span class="n">h</span><span class="o">.</span><span class="n">Uid</span><span class="p">)},</span>
		<span class="p">{</span><span class="s">"gid"</span><span class="p">,</span> <span class="n">strconv</span><span class="o">.</span><span class="n">Itoa</span><span class="p">(</span><span class="n">h</span><span class="o">.</span><span class="n">Gid</span><span class="p">)},</span>
		<span class="p">{</span><span class="s">"size"</span><span class="p">,</span> <span class="n">strconv</span><span class="o">.</span><span class="n">FormatInt</span><span class="p">(</span><span class="n">h</span><span class="o">.</span><span class="n">Size</span><span class="p">,</span> <span class="m">10</span><span class="p">)},</span>
		<span class="p">{</span><span class="s">"mtime"</span><span class="p">,</span> <span class="n">strconv</span><span class="o">.</span><span class="n">FormatInt</span><span class="p">(</span><span class="n">h</span><span class="o">.</span><span class="n">ModTime</span><span class="o">.</span><span class="n">UTC</span><span class="p">()</span><span class="o">.</span><span class="n">Unix</span><span class="p">(),</span> <span class="m">10</span><span class="p">)},</span>
		<span class="p">{</span><span class="s">"typeflag"</span><span class="p">,</span> <span class="kt">string</span><span class="p">([]</span><span class="kt">byte</span><span class="p">{</span><span class="n">h</span><span class="o">.</span><span class="n">Typeflag</span><span class="p">})},</span>
		<span class="p">{</span><span class="s">"linkname"</span><span class="p">,</span> <span class="n">h</span><span class="o">.</span><span class="n">Linkname</span><span class="p">},</span>
		<span class="p">{</span><span class="s">"uname"</span><span class="p">,</span> <span class="n">h</span><span class="o">.</span><span class="n">Uname</span><span class="p">},</span>
		<span class="p">{</span><span class="s">"gname"</span><span class="p">,</span> <span class="n">h</span><span class="o">.</span><span class="n">Gname</span><span class="p">},</span>
		<span class="p">{</span><span class="s">"devmajor"</span><span class="p">,</span> <span class="n">strconv</span><span class="o">.</span><span class="n">FormatInt</span><span class="p">(</span><span class="n">h</span><span class="o">.</span><span class="n">Devmajor</span><span class="p">,</span> <span class="m">10</span><span class="p">)},</span>
		<span class="p">{</span><span class="s">"devminor"</span><span class="p">,</span> <span class="n">strconv</span><span class="o">.</span><span class="n">FormatInt</span><span class="p">(</span><span class="n">h</span><span class="o">.</span><span class="n">Devminor</span><span class="p">,</span> <span class="m">10</span><span class="p">)},</span>
	<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The <code class="language-plaintext highlighter-rouge">tarsumHash</code> struct is interesting, in that it is a customized <code class="language-plaintext highlighter-rouge">hash.Hash</code> implementation.
It has a special <a href="https://github.com/moby/buildkit/blob/5d47024a21ec128c8caf7af2ab4b83d63d9a4094/cache/contenthash/filehash.go#L85"><code class="language-plaintext highlighter-rouge">Reset()</code></a> function that when called resets the hash state to default, then “primes” the hash object with header properties in the V1 tarsum format.</p>

<p>This code flow is interesting because only the file metadata is processed, not the contents of the file.
Only once the header hash been generated then the file contents are hashed.</p>

<hr />

<h3 id="docker-takeaways">Docker Takeaways</h3>

<p>Docker builds are processes by BuildKit, which creates a DAG of build steps and layers.
The resources and properties required to generate the layer, including references to the previous layer, are included in the graph of a docker image build.
BuildKit is able to prune subgraphs (layers/work) from the graph by comparing nodes to stored cache entries.</p>

<p>As a result of the build graph caching system, dockerfile often include idiomatic code to better separate layers and place more stable (unchanging) layers earlier in the build process.
One example often seen in python and golang images is pre-loading libraries/modules before the rest of the source code.
The library references are usually modified less than the code itself, leading better caching and faster image builds.</p>

<details>
<summary>Dockerfile bind mount examples</summary>
  <div>

    <div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> python:3</span>
<span class="k">WORKDIR</span><span class="s"> /src</span>

<span class="c"># Temporarily use requirements.txt</span>
<span class="k">RUN </span><span class="nt">--mount</span><span class="o">=</span><span class="nb">type</span><span class="o">=</span><span class="nb">bind</span>,source<span class="o">=</span>requirements.txt,target<span class="o">=</span>/tmp/requirements.txt <span class="se">\
</span>    pip <span class="nb">install</span> <span class="nt">--requirement</span> /tmp/requirements.txt

<span class="c"># Now copy over rest of code in a new layer</span>
<span class="k">COPY</span><span class="s"> . .</span>
<span class="c"># continue...</span>
</code></pre></div>    </div>

    <p>and in golang:</p>

    <div class="language-dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> go:1.24</span>
<span class="k">WORKDIR</span><span class="s"> /src</span>

<span class="c"># Download modules first</span>
<span class="k">RUN </span><span class="nt">--mount</span><span class="o">=</span><span class="nb">type</span><span class="o">=</span><span class="nb">bind</span>,source<span class="o">=</span>go.mod,target<span class="o">=</span>/src/go.mod <span class="se">\
</span>    <span class="c"># including the go.sum file ensures the downloaded modules match what we expect.</span>
    --mount=type=bind,source=go.sum,target=/src/go.sum \
    go mod download

<span class="c"># Copy project files over and build</span>
<span class="k">COPY</span><span class="s"> . .</span>
<span class="k">RUN </span>go build
</code></pre></div>    </div>

  </div>
</details>

<p>As part of the build-level caching, files are a core part of the cache.
I found that not all aspects of the file are considered when cached.</p>

<p>Specific to docker’s file caching:</p>

<ul>
  <li>File contents</li>
  <li>File name, mode, size, type,</li>
  <li>File <em>Modified Time</em></li>
  <li>uid/gid, uname/gname</li>
  <li>Any additional <a href="https://www.gnu.org/software/tar/manual/html_node/Extended-File-Attributes.html">GNU tar Extended File Attributes</a> (custom name-value pairs associated with file)</li>
</ul>

<p>To help inform developers on how to best interact with the cache, the docker doc <a href="https://docs.docker.com/build/cache/optimize/#keep-the-context-small">Optimize cache usage in builds</a> exists.
It covers a few ways to improve cache performance, including:</p>

<ul>
  <li><a href="https://docs.docker.com/build/cache/optimize/#order-your-layers">layer ordering</a>- things with more frequent changes should be placed nearer to the end of the dockerfile.</li>
  <li><a href="https://docs.docker.com/build/cache/optimize/#keep-the-context-small">minimizing context</a> - Use <code class="language-plaintext highlighter-rouge">.dockerignore</code> to minimize files included in the build context.</li>
  <li><a href="https://docs.docker.com/build/cache/optimize/#use-bind-mounts">use bind mounts</a> - minimize extra copy instructions prepping files for later instructions</li>
  <li><a href="https://docs.docker.com/build/cache/optimize/#use-cache-mounts">Use cache mounts</a> - cache mounts allow adding  specific files to the cache.  This cache is interesting because unlike the layer cache, it does not depend on previous layers.  It is stored outside the build, and sharable across multiple builds.</li>
  <li><a href="https://docs.docker.com/build/cache/optimize/#use-an-external-cache">Use external cache</a> - By default, the cache is specific to the builder instance being used.  An external cache allows defining a remote cache source.  This is most often seen in CI/CD pipelines, and allows easy reuse across workflows.  More information can be found in the <a href="https://docs.docker.com/build/cache/backends/">Cache storage backends</a> doc.</li>
</ul>

<h2 id="rushjs-build-caching">RushJS Build Caching</h2>

<p>Another build tool I investigated was <a href="https://rushjs.io/pages/intro/welcome/">RushJS</a>, a tool used to building and managing many NPM packages from a single repo.
It is part of the <a href="https://rushstack.io/">Rush Stack</a> family of projects, which is managed by <a href="https://github.com/microsoft/rushstack">Microsoft</a>.
Along with managing dependencies, it uses caching and incremental builds to speed up building projects, especially those with complex dependency chains.</p>

<p>The article <a href="https://rushjs.io/pages/maintainer/build_cache/">Enabling the build cache</a> describes the caching design and methodology.
Rush uses two types of strategies for limiting build work:</p>

<ul>
  <li><a href="https://rushjs.io/pages/advanced/incremental_builds/">Incremental build</a> analyzer that enables skipping projects whose file contents have not changed since the last build.  This does not preserve build output.</li>
  <li>“cache restoration”, where tar archives of project build artifacts are restored from cache.</li>
</ul>

<h3 id="incremental-builds">Incremental Builds</h3>

<p>incremental builds: https://rushjs.io/pages/advanced/incremental_builds/</p>

<p>The incremental build system allows <code class="language-plaintext highlighter-rouge">rush build</code> to skip projects that are up to date.
The “update to date” rules are:</p>

<ol>
  <li>The project has already been built locally, AND</li>
  <li>Its input files and NPM dependencies have not changed since then, AND</li>
  <li>If the project depends on any other Rush projects, those projects are up to date as well, AND</li>
  <li>The command line parameters haven’t changed. (For example, invoking <code class="language-plaintext highlighter-rouge">rush build --production</code> after <code class="language-plaintext highlighter-rouge">rush build</code> would require rebuilding.)</li>
</ol>

<p>It respects and <code class="language-plaintext highlighter-rouge">.gitignore</code> files in the project folder
Configuration options are stored in [<your project="">/config/rush-project.json](https://rushjs.io/pages/configs/rush-project_json/)</your></p>

<p>Changes to input files are determined by file content hashes.
All other file metadata seems to be completely ignored.
Since modifying a filename would require modifying the files that references it, creating a soft-requirement to have matching filenames.</p>

<p>This feature does not concern itself with build outputs, so if all the inputs match the cache then the rest of the build for that piece is skipped.
If the build artifacts are modified outside of the build process they will remain modified even if a build is skipped.</p>

<h3 id="cache-restoration">Cache Restoration</h3>

<p>The cache storage is file-based and stored separate from source control.
It is default-disabled, and is configured by <a href="https://rushjs.io/pages/configs/build-cache_json/">common/config/rush/build-cache.json</a>.
Since Rush is designed for large monorepos, the cache is stored in <code class="language-plaintext highlighter-rouge">common/temp/build-cache</code> by default.
They do note that the folder can be manually placed outside the repository to share between projects.
Cache is also able to be centrally stored in a “cloud-hosted storage container” that can be accessed by users.
The options are <code class="language-plaintext highlighter-rouge">azure-blog-storage</code>, <code class="language-plaintext highlighter-rouge">amazon-s3</code>, and <code class="language-plaintext highlighter-rouge">http</code>.
It seems to be compatible with any storage that speaks <code class="language-plaintext highlighter-rouge">aws-s3</code>.
The <code class="language-plaintext highlighter-rouge">http</code> provider allows using a normal webserver to serve cache objects.</p>

<p>The cache entry is keyed on various project inputs:</p>

<ul>
  <li>Hashes of source files that are under the project’s folder, ignoring any files excluded by <code class="language-plaintext highlighter-rouge">.gitignore</code></li>
  <li>Hashes of source files under other workspace projects that are dependencies of the project. This applies to cache restoration strategy but not output preservation strategy</li>
  <li>the versions of all external NPM packages that your project depends on, including indirect dependencies</li>
  <li>the Rush command-line parameters used to perform the operation</li>
</ul>

<p>It also allows for fine-grained configuration of the cache key in the [<your project="">/config/rush-project.json](https://rushjs.io/pages/configs/rush-project_json/) and [common/config/rush/build-cache.json](https://rushjs.io/pages/configs/build-cache_json/) files.
This allows one to specify environment variables and additional files/glob patterns that should be considered.
The `cacheEntryNamePattern`is able to configured to also include tokens such as `[os]` or`[arch]`, but must always contain a `[hash]` token.</your></p>

<h3 id="rushjs-takeaways">RushJS Takeaways</h3>

<p>Things I liked:</p>

<ul>
  <li>multi-pronged approach to analyze different parts of the build
    <ul>
      <li>separate incremental build and output caching actions</li>
    </ul>
  </li>
  <li>output preservation calculated by the incremental build analyzer, and applied by the build cache.
    <ul>
      <li>If no <em>input</em> files were modified compared to the previous build, then the project is skipped.</li>
      <li>Doesn’t concern itself with build artifacts</li>
    </ul>
  </li>
  <li>cache restoration
    <ul>
      <li>query cache based on inputs to determine if output files can be replaced by cached+compressed build artifacts</li>
    </ul>
  </li>
  <li>file-based cache storage
    <ul>
      <li>separate cache folder that can be shared across projects</li>
      <li>provides integrations to remote storage options</li>
      <li>Also able to check in to source control</li>
      <li>Build cache artifacts stored in compressed tar files</li>
    </ul>
  </li>
  <li>Cache is keyed on input file hashes, lib versions, build parameters, and optional ENV vars.</li>
  <li>respects <code class="language-plaintext highlighter-rouge">.gitignore</code> files</li>
</ul>

<h2 id="final-thoughts">Final Thoughts</h2>

<p>A key part of creating cache is properly tracking the build context of input and output artifacts.
At a minimum record the hash of file data, but metadata like timestamps can create unneeded cardinality.
Files may also need to be minimally processed by the build system to extract libraries and other program references.
The context should also includes build environment properties that are able to modify the output of the build, such as environment variables and system architecture.</p>

<p>It can be valuable to also track and store output artifacts, which can be re-used when the inputs allow.
User should never be interacting directly with cache files, so file compression can be used to both save disk space and obfuscate files from idle meddling.
The storage location for cache files should consider how re-usable a cache entry is, scoping to project, repo, or system.</p>

<p>Users should be provided a few levers to control the cache behavior, such as a build flag to ignore the cache.
There should also be ways to filter things from being cached, either through flags, tool config, or existing config (<code class="language-plaintext highlighter-rouge">.gitignore</code>).</p>

<h2 id="further-reading">Further Reading</h2>

<ul>
  <li><a href="https://otero.gitbooks.io/cmake-complete-guide/content/chapter-3.html">CMake Complete Guide Chapter 3</a> - cache entries keyed to the variable scope</li>
  <li><a href="https://www.justanotherdot.com/posts/avoid-build-cache-bloat-by-sweeping-away-artifacts.html">Avoid Cache Bloat</a> - avoid keeping entries that are unlikely to be referenced</li>
  <li><a href="https://learn.microsoft.com/en-us/azure/architecture/best-practices/caching">Azure Caching Guidance</a> - apply cloud architecture principles to software architecture</li>
</ul>]]></content><author><name>Brooke Byers</name><email>ByersBr@pm.me</email></author><category term="[&quot;Programming&quot;]" /><summary type="html"><![CDATA[This post explores Docker's build caching system using BuildKit and LLB to optimize layer reuse via metadata-based hashing, alongside RushJS's incremental builds and cache restoration for monorepos, emphasizing strategies to minimize redundant work. Key takeaways include metadata-driven caching, context management, and tools for efficient artifact reuse across complex projects.]]></summary></entry><entry><title type="html">Packaging and Releasing a Software Project</title><link href="https://b13rg.icecdn.tech//packaging-software/" rel="alternate" type="text/html" title="Packaging and Releasing a Software Project" /><published>2025-04-23T00:00:00+00:00</published><updated>2025-04-23T00:00:00+00:00</updated><id>https://b13rg.icecdn.tech//packaging-software</id><content type="html" xml:base="https://b13rg.icecdn.tech//packaging-software/"><![CDATA[<p><img src="/images/softwarePkg/Le-Quai-des-Pâquis-à-Genève.jpg" alt="Le Quai des Pâquis à Genève - Jean-Baptiste-Camille Corot - 1842" /></p>

<p>This is a collection of notes and thoughts on developing and releasing an open source software package.
It is based on the process I went through releasing <a href="https://github.com/ice-bergtech/kr8">kr8+</a>, a golang tool that processes jsonnet into output files.</p>

<h2 id="readmemd">Readme.md</h2>

<p>The readme is usually the first thing a module consumer sees.</p>

<p>It should provide a simple, succinct summary of what the tools does and who it is meant for.
It should funnel the dense information from code and docs into an easy-to-understand format.
Writing a readme is an entire process, beyond this article.</p>

<p><a href="https://github.com/hackergrrl/art-of-readme">Art of README</a> describes the properties of a good readme and goals it should try to achieve.
A useful guide/spec that digs into key element is <a href="https://github.com/RichardLitt/standard-readme/blob/main/spec.md">Richart Litt’s standard-readme spec</a>.</p>

<p>Form a high-level, the important sections are:</p>

<ul>
  <li>Description - what it is, what it does, what it solves</li>
  <li>Background - why it exists</li>
  <li>User Installation and usage - How to install and use</li>
  <li>Documentation links - Where to learn more about functionality</li>
  <li>Developer Setup - How to get the repo setup locally and contribute</li>
  <li>License information - Note licenses used by the code.</li>
</ul>

<h2 id="building">Building</h2>

<h3 id="scripting-common-tasks">Scripting Common Tasks</h3>

<p>Any common commands that a developer runs should be committed alongside the code.
It helps make sure everyone is running the same tests and commands and (hopefully) getting the same results.</p>

<p>In the case of kr8+, I setup a taskfile to capture setup and common commands.
It is similar to a makefile, but a little more verbose and structured as a yaml file.</p>

<p>When documenting these common commands, make sure you capture:</p>

<ul>
  <li>Dev environment setup - tools, submodules</li>
  <li>Building - code, docs</li>
  <li>Testing - unit / integration tests</li>
</ul>

<p><a href="https://github.com/ice-bergtech/kr8/blob/main/Taskfile.yml">kr8+ example</a></p>

<h3 id="generating-documentation">Generating documentation</h3>

<p>Writing documentation is difficult because of how quickly it can become out of date.
By generating documentation directly from source code it will accurately reflect the current state of the code.
It should be treated as an additional artifact of the code, and always generated along with binaries.</p>

<p><a href="https://pkg.go.dev/golang.org/x/tools/cmd/godoc">Godoc</a> is useful for documenting Golang code.
It extracts comments and formats then into a friendly html file.</p>

<p>There are sometimes tool or framework-specific tool that can generate further documentation.
In golang, cobra has tools to generate docs for commands.
This is great for creating documentation targeted towards end users.
Once generated, the documentation should be published in a way that is easy to consume for the intended audience.
Tools like <a href="https://www.mkdocs.org/getting-started/">MkDocs</a> package up markdown files into a standard website.</p>

<p>Here’s example of generating package docs for <a href="https://github.com/ice-bergtech/kr8/blob/main/docs/docs.go">kr8+</a>.</p>

<h2 id="organizing-code">Organizing Code</h2>

<p>Organize into logical partitions.
Group similar functionality together, and 
Create clear boundaries of ownership within the code and helps reduce mental load when reasoning about functionality.</p>

<p>Separate user-facing code from internal logic.</p>

<p>Extract functions
Keep function short and to the point.</p>

<h3 id="tests">Tests</h3>

<p>Tests ensure the program is doing what you think it does.
They should be created at different points horizontally across functions and vertically across code layers.</p>

<p>Starting from 0 is difficult, 
Focus on package “root” and “leaf” functions and work your way in.</p>

<ul>
  <li>Packages and functions that provide core functionality</li>
  <li>User-interaction layer, to compare inputs to expected outputs.</li>
</ul>

<p>Test outside of the code with integration tests.
Make sure the built tool operates as expected on a basic level.
Test the interactions users and other tools will make.
They can also verify assumptions the code makes like the existence of certain external resources.</p>

<p>This provides a final check to make sure the tool operates as it’s supposed to.
In the case of kr8+ it was valuable to generate examples with the tool.
It showed how to actually use the tool and ensures the tool is working as expected.</p>

<h2 id="formatting">Formatting</h2>

<p>Standard formatting makes it easier to read code through a consistent style throughout the codebase.
It can also act as a “soft barrier” to change requests, ensuring basic checks have been made.</p>

<p>Automated formatters in editors and part of build process to keep things standard.
It frees up the writer to focus on the code instead of properly balancing tabs and braces.</p>

<p>An <a href="https://editorconfig.org/">.editorconfig</a> file can help “maintain consistent coding styles for multiple developers working on the same project across various editors and IDEs”.
It is mostly editor-agnostic, though some like vscode require a <a href="https://open-vsx.org/extension/EditorConfig/EditorConfig">plugin</a>.</p>

<p><a href="https://github.com/ice-bergtech/kr8/blob/main/.editorconfig">kr8+ example</a></p>

<h3 id="linting">Linting</h3>

<p>Use tools that can be easily configured and ran.
Very language specific,
For golang there’s <a href="https://golangci-lint.run/welcome/install/">golangci-lint</a> which packages up various tools.</p>

<p>I find it is useful to have the linting tool generate a summary output file, and check that into source control.
It’s not always reasonable to fix every single issue, especially on a WIP branch.
If you create exceptions, make them as specific as possible to avoid swallowing unrelated issues.</p>

<p>Some of the golang ones I found most useful:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">exhaustruct</code> - makes sure struct fields are all initialized.  Enabled as needed to check.</li>
  <li><code class="language-plaintext highlighter-rouge">funlen</code>/<code class="language-plaintext highlighter-rouge">cyclop</code>/<code class="language-plaintext highlighter-rouge">gocognit</code> - help identify complex functions that should be refactored and split up.</li>
  <li><code class="language-plaintext highlighter-rouge">dupl</code> - detect duplicate fragments of code</li>
  <li><code class="language-plaintext highlighter-rouge">gochecknoglobals</code> - minimize global variables</li>
  <li><code class="language-plaintext highlighter-rouge">gosec</code>/<code class="language-plaintext highlighter-rouge">govet</code> - find potential security problems</li>
</ul>

<p><a href="https://github.com/ice-bergtech/kr8/blob/main/.golangci.yml">kr8+ example</a></p>

<h2 id="attributinglicenses">Attributing/Licenses</h2>

<p>Properly attribute sources of code.
Call out contributors.</p>

<p>Code references.
Add links to references for code.
Even once the links rot, they can still provide some context for confusing chunks of code.</p>

<p>Finally, it can be useful to link to other similar projects.
good for comparing functionality, and provides easy links in the future to discover ways to improve your own project.</p>

<p>True OSS licenses are preferred.</p>

<h2 id="releasing">Releasing</h2>

<p>With the code done up with a bow, it’s time to consider making it widely available.
Packages can be published in a variety of formats, so choose one that is more ergonomic for your end users.</p>

<p>In the case of kr8+, I opted for a homebrew tap to enable easy installation on Mac and Linux.</p>

<h3 id="setting-up-homebrew-tap">Setting up Homebrew Tap</h3>

<p>A tap is just a repo with some special files in it.
<a href="https://docs.brew.sh/How-to-Create-and-Maintain-a-Tap">official docs</a></p>

<p>Homebrew taps do have a special naming convention, where if a repo is hosted on github “we recommend that the repository’s name start with <code class="language-plaintext highlighter-rouge">homebrew-</code> so the short <code class="language-plaintext highlighter-rouge">brew tap</code> command can be used”.
Unless you are making multiple taps, it is easiest to just name the repo <code class="language-plaintext highlighter-rouge">homebrew-tap</code>.</p>

<p>Use the <code class="language-plaintext highlighter-rouge">brew tap-new</code> command to initialize a starter repo.
Update the readme and license as needed.
In the case of publishing kr8+, I never interact with the repo directly, instead it is used as a target for GoReleaser builds.</p>

<p>Once setup, users will reference the tap in the format <code class="language-plaintext highlighter-rouge">brew install &lt;username&gt;/tap/&lt;formula&gt;</code> (in the case of github&gt;) or the longer <code class="language-plaintext highlighter-rouge">brew install &lt;repo url&gt;/&lt;formula&gt;</code>.</p>

<p><a href="https://github.com/ice-bergtech/homebrew-tap">kr8+ example</a></p>

<h3 id="configuring-goreleaser">Configuring GoReleaser</h3>

<p>GoReleaser “is a release automation tool.
It currently supports Go, Rust, Zig, and TypeScript (with Bun and Deno).”a tool that packages and releases Golang code in a variety of output formats.”
Documentation for configuring is <a href="https://goreleaser.com/customization/">here</a>.</p>

<p>For kr8+ it was configured to produce:</p>

<ul>
  <li>Build packages for <code class="language-plaintext highlighter-rouge">linux</code> and <code class="language-plaintext highlighter-rouge">darwin</code></li>
  <li><code class="language-plaintext highlighter-rouge">tar.gz</code> archives</li>
  <li><code class="language-plaintext highlighter-rouge">rpm</code>, <code class="language-plaintext highlighter-rouge">deb</code>, and <code class="language-plaintext highlighter-rouge">apk</code> packages.</li>
  <li>SBOM and Checksums</li>
  <li>Publish package to homebrew</li>
</ul>

<p>Additionally, automation was setup to only run the full build+publish when a new git tag is added.</p>

<p><a href="https://github.com/ice-bergtech/kr8/blob/main/.goreleaser.yml">kr8+ example</a></p>

<h4 id="github-personal-access-token-pat">Github Personal Access Token (PAT)</h4>

<p>For publishing on github with automation, you should create a <a href="https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens">Personal Access Token</a>.
It can be stored as a repo secret and be consumed by automation to enable publishing release artifacts.
In the case of kr8+, it uses GoReleaser to publish the artifacts to a <a href="https://github.com/ice-bergtech/homebrew-tap">homebrew tap</a> in a separate repo.</p>

<p>To create the fine-grained token, navigate to <a href="https://github.com/settings/personal-access-tokens">github.com/settings/personal-access-tokens</a>.</p>

<p>the token will require:</p>

<p>Read access to:</p>

<ul>
  <li>metadata</li>
  <li>secrets</li>
  <li>variables* environments</li>
  <li>issues</li>
</ul>

<p>Read <em>and</em> Write access to:</p>

<ul>
  <li>code</li>
  <li>pull requests</li>
  <li>commit statuses</li>
  <li>actions</li>
</ul>

<p>Also valuable to limit what repositories the token can access to limit blast radius if things go sideways.
When in doubt, it’s often easier to create a new token instead of trying to get an existing one to work properly.</p>

<h3 id="actions-and-workflows-automation">Actions and Workflows Automation</h3>

<p>Run same tests and checks a developer would run.
Make sure everything checks out, especially after merging into main.
In cases of formatting actions, it can be valuable to allow modifying PRs to provide quick fixes to an annoying issue.</p>

<h3 id="golang-pkgsite">Golang pkgsite</h3>

<p>Part of the golang ecosystem is part of the golang documentation system.
It allows publishing package documentation in a central place.
It will parse the project repo and extract documentation just like Godoc.
You can learn more on it’s <a href="https://pkg.go.dev/about">about page</a>.</p>

<p>To add a package, navigate to https://pkg.go.dev/<repo> and you can click a button to have the repo scraped.
You can also sync it by using `proxy.golang.org` as a proxy when fetching a module.
For example:</repo></p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">GOPROXY</span><span class="o">=</span>https://proxy.golang.org <span class="nv">GO111MODULE</span><span class="o">=</span>on go <span class="nb">install </span>github.com/ice-bergtech/kr8@v0.0.8
</code></pre></div></div>

<p><a href="https://pkg.go.dev/github.com/ice-bergtech/kr8">kr8+ example</a></p>

<h2 id="other-random-things">Other Random Things</h2>

<ul>
  <li>Project logo - A image to associate with the project</li>
  <li>Changelog.md - A list of past changes between version - <a href="https://github.com/ice-bergtech/kr8/blob/main/CHANGELOG.md">kr8+ example</a></li>
  <li>Features.md - A list of current project features and functionality - <a href="https://github.com/ice-bergtech/kr8/blob/main/FEATURES.md">kr8+ example</a></li>
  <li>Roadmap.md - A list of future planned projects and improvements - <a href="https://github.com/ice-bergtech/kr8/blob/main/ROADMAP.md">kr8+ example</a></li>
  <li>Project domain - A custom domain to host docs</li>
  <li>Funding - github sponsors, librepay, patreon etc.</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>Releasing a project includes a lot of things beyond the raw code.
There are a lot of supporting pieces that should be in place to best service the end user.
When preparing a project it is important to analyze it through different perspectives.
You should consider all the types of end users that may view the project and what they find important.
The added context and documentation helps users solve their issues faster and hopefully on their own.</p>

<p>Finally, always be improving a project but also know when to move on.
It is better to whole-ass one thing instead of half-assing two things.</p>

<h2 id="references">References</h2>

<ul>
  <li><a href="https://github.com/ice-bergtech/kr8">kr8+</a></li>
  <li><a href="https://editorconfig.org/">EditorConfig</a></li>
  <li><a href="https://www.mkdocs.org/getting-started/">MkDocs</a></li>
  <li><a href="https://kubernetes.io/docs/contribute/style/style-guide/">Kube Documentation Style guide</a></li>
  <li><a href="https://golangci-lint.run/">Golangci-lint</a></li>
  <li><a href="https://goreleaser.com/customization/">GoReleaser</a></li>
  <li><a href="https://carlosbecker.com/posts/goreleaser-rust-zig/">Using GoReleaser - Carlos Becker</a></li>
  <li><a href="https://docs.brew.sh/">Homebrew docs</a></li>
</ul>]]></content><author><name>Brooke Byers</name><email>ByersBr@pm.me</email></author><category term="[&quot;Programming&quot;]" /><summary type="html"><![CDATA[This is a collection of notes and thoughts on developing and releasing an open source software package.]]></summary></entry><entry><title type="html">Infrastructure as Code at Scale</title><link href="https://b13rg.icecdn.tech//Infra-as-Code/" rel="alternate" type="text/html" title="Infrastructure as Code at Scale" /><published>2025-04-15T00:00:00+00:00</published><updated>2025-04-15T00:00:00+00:00</updated><id>https://b13rg.icecdn.tech//Infra-as-Code</id><content type="html" xml:base="https://b13rg.icecdn.tech//Infra-as-Code/"><![CDATA[<p><img src="/images/iac/bmc_software.jpg" alt="BMC Software Shirt" /></p>

<p>The Open Systems Interconnection (OSI) model is a conceptual model created by ISO that describes how information flows through different layers in a network system.
This post describes a similar model that applies to architecting Infrastructure as Code (IaC) at Scale.
The layers do not correspond 1-1 to tools and practices used today, but they provide a useful guide for deploying and managing a diverse set of of applications with distinct compute and data requirements.</p>

<p>This is meant to be applied to large deployments, beyond a single cluster or two.
The model attempts to encompass most aspects of managing complex deployments of applications and services across multiple environments.
A “normal” deployment under this model could have <code class="language-plaintext highlighter-rouge">n regions * m stack types * a applications * r replicas * z Disaster Recovery ratio</code>.
Beyond the deployment there are also security, regulatory and contractual requirements that influence the design.</p>

<p>By partitioning IaC into layers, we can better understand how different aspects of are configured,  monitored, and secured.
Each layer will have a difference balance of manual vs. “codified” actions.
IaC should aim to be ergonomic and scalable for the operators and maintainers.
It should be thought of as weaving together different threads of infrastructure into a cohesive fabric that provides a stable foundation for applications to be built on.</p>

<h2 id="iac-reference-model">IaC Reference Model</h2>

<h3 id="layer--1-billing">Layer -1: Billing</h3>

<p>Someone’s getting paid somewhere.
The billing underlies all the layers.
To cost to participate in modern “IP Networks” can vary wildly depending on your needs and how you execute.
Most of the time this requires a credit card, but there are growing crypto-backed operations that have greater separation from the traditional financial systems.
There is always self-hosted intranet options, but there are still raw resource requirements.</p>

<p>This will include things like:</p>

<ul>
  <li>Cloud provider accounts</li>
  <li>Domain name registry</li>
  <li>ASN/IP Registry</li>
  <li>Self-hosted electricity/internet/hardware</li>
</ul>

<h4 id="monitoring--security">Monitoring / Security</h4>

<p>While there are ways to manage these with IaC, it often needs manual intervention and verification.
The best automation for this layer is email rules for getting your attention.
Core accounts should trigger emails when logged into or updated.</p>

<p>Strong passwords are extremely important.
Make them as long as possible, and use a password manager.</p>

<p>You should also take advantage of Whois (now <a href="https://openrdap.org/">RDAP</a>) privacy and <a href="https://www.arin.net/resources/manage/rpki/">RPKI services</a>.
With enough justification, these can be self-hosted but this is not a common approach.</p>

<h3 id="layer-0-privilege">Layer 0: Privilege</h3>

<p>The privilege layer manages access to the cloud provider though Identity Access Management (IAM).
To use a service, you usually need an account to deploy resources into.</p>

<p>For 1-2 accounts, this can be done manually, but for any more you should require IaC.
While changes are made very rarely, changes here effect core pieces of how all other layers operate.
It also helps version control the changes for easy reference and rollback.</p>

<p>Constructing a tree of least-privilege.</p>

<h3 id="layer-1-cluster-network">Layer 1: Cluster Network</h3>

<p>This network is used for private communication between nodes and resources.
For very simple deployments this may be contained within a single node or VPC.
For larger deployments, this will encompass multiple nodes, networks, and cloud providers.</p>

<p>For anything more complex than a single VPC you will need to start using tunnels to create your network.
Traditionally IPSec tunnels were used, but more recently Wireguard tunnels have become popular for being simpler and more efficient.</p>

<ul>
  <li><a href="https://tailscale.com/kb/1151/what-is-tailscale">Tailscale</a></li>
  <li><a href="https://docs.netmaker.io/docs/about">Netmaker</a></li>
  <li><a href="https://docs.aviatrix.com/documentation/latest/getting-started/platform-overview/index.html?expand=true">Aviatrix</a></li>
  <li><a href="https://www.wireguard.com/quickstart/#command-line-interface">Raw Wireguard</a></li>
</ul>

<h4 id="structure">Structure</h4>

<p>Separate “tiers” of cluster networks statically define network with varying levels of internal and external access.
Resources in the other layers should be able to reference these tiers for baseline configuration.
The tiers should help reinforce secure resource isolation to limit attack surface.</p>

<ul>
  <li>Core - Command and Control resources, including internal-facing <a href="#layer-5-application-network">Application Networks</a></li>
  <li>Internal - <a href="#layer-6-application">Applications</a> and client services</li>
  <li>External - DMZ-type tier, containing external-facing <a href="#layer-5-application-network">Application Network</a> pieces</li>
</ul>

<h3 id="layer-2-permissions">Layer 2: Permissions</h3>

<p>Setup authentication and <em>operational</em> access to cloud resources.
This set of access permissions allow API access for yourself and automation.
It is distinct from the operational access permissions in Layer 1 in that this layer focuses on <em>operational</em> access rather than management access.</p>

<p>This encompasses managing authentication and authorization for your cloud resources and applications.
It usually ends up being a conglomeration of a few different tools that manage different pieces of that auth like SSH keys, SSO, and IAM roles.</p>

<p>Centralize management of authentication, authorization, accounting.</p>

<h3 id="layer-3-compute">Layer 3: Compute</h3>

<p>Raw instances and services that run your applications.
Holistically, this can be anything that transforms an input to output but practically it’s an internet-connected CPU with some amount of usable RAM.</p>

<ul>
  <li>Physical Nodes - Servers, Raspberry Pis, Android phones</li>
  <li>Virtual Machines - EC2 Instances, Droplets, VMs</li>
  <li>Cluster-as a service - EKS, GKE, AKS</li>
  <li>Serverless - Lambda, FaaS</li>
</ul>

<p>Broadly organize work to minimize resources needed to complete task, but don’t over-optimize.
For clusters, consider consolidate or split up nodes to keep average utilization high while also absorbing spikes.</p>

<p>With modern cloud computing, the bottleneck is often memory but this is directly tied to workloads being ran.
With enough integration of information about the infrastructure, you can optimize for $/mem, $/cpu, or $/egress.</p>

<h3 id="layer-4-storage">Layer 4: Storage</h3>

<p>Applications often need to persist data beyond the lifetime of the program.
Each one has it’s own special way and practices, but they can be roughly grouped into:</p>

<ul>
  <li>Block/File/Object storage (cluster-local or managed)</li>
  <li>Databases</li>
  <li>Backup+Restore process</li>
  <li>Caching</li>
</ul>

<p>Data locality is also a key concern for performance and security.
Data locality is the practice of keeping data close to where it’s needed, reducing latency and improving efficiency.
Storage resources are located:</p>

<ol>
  <li>Node/cluster local - Share compute with application</li>
  <li>Cluster cluster - Clusters managed by you, backed by raw provider</li>
  <li>Managed service - DB-as-a-Service</li>
</ol>

<p>Keeping data close to where it’s needed also helps reduce costs, as you aren’t burning egress fees shipping bits around needlessly.</p>

<h4 id="persistence">Persistence</h4>

<p>Data is large, arbitrary, and often unstructured.
Data should be stored in a way that is scalable, durable, and secure.
It should also attempt to minimize duplication.
Filesystems like ZFS or Btrfs can be configured to deduplicate and compress data at the block level.
Backups should also follow the incremental “snapshot” paradigm, with full backups being taken at regular intervals.</p>

<p>When storing the data long-term, <code class="language-plaintext highlighter-rouge">3-2-1</code> principle should be followed:</p>

<ul>
  <li>3 copies of the data</li>
  <li>across 2 mediums</li>
  <li>with 1 copy off site</li>
</ul>

<p>This ensures data is available even if one of the mediums fails.
Of course in the event of some incident, the issue becomes is restoring data in a timely predictable manner.</p>

<h3 id="layer-5-application-network">Layer 5: Application Network</h3>

<p>This layer manages application-external access.
selectively links parts of the application available to external networks.
You “target audience” may be the wider internet, internal clients/teams, or other cluster applications.</p>

<p>This is separate from <a href="#layer-1-cluster-network">Cluster Network</a>, in that it’s focused on the application itself, rather than supporting the core communication of the cluster as a whole.
These pieces are usually deployed alongside the application resources they are supporting.</p>

<p>Here are some examples of components in this layer:</p>

<ul>
  <li>Application DNS - Usually <code class="language-plaintext highlighter-rouge">A</code> and <code class="language-plaintext highlighter-rouge">AAAA</code> records, but includes <code class="language-plaintext highlighter-rouge">MX</code>, <code class="language-plaintext highlighter-rouge">TXT</code> and others</li>
  <li>Load balancers - Distributed balancers to route traffic to backend nodes</li>
  <li>Ingress gateways - Manages routing rules at L3(Layer 3) and L4(TCP/UDP/etc.) + L7(HTTP(S)/DNS)</li>
  <li>Proxy / Firewalls / Filtering Policy - Manages traffic flow and security at the application level</li>
</ul>

<h3 id="layer-6-application">Layer 6: Application</h3>

<p>These are the actual applications that run on the infrastructure.
There is an extreme variety of deployment methods, so it is usually best to focus on a way that is ergonomic for both the application and maintainer.</p>

<p>The application itself is beyond the purview of this article, but it should aim to be encapsulated in a easy-to-consume manner.
Docker containers seem to have become the most popular, but can be overkill depending on the purpose of the application deployment.
Whatever solution used, as much of the configuration as possible should be statically defined.</p>

<p>Documentation <strong>needs</strong> to be included.
If something isn’t documented, it doesn’t exist.
The same configuration used to prep and deploy the application can be used to provide greater documentation context.</p>

<h3 id="layer-65-inter-applications-integrations">Layer 6.5: Inter-Applications Integrations</h3>

<p>With multiple applications comes the interaction of applications with each other.
This layer encodes the services and resources an application makes available, and how they can be consumed.
The configuration is highly application-dependent and involves creating “glue” configuration between applications, often through a service mesh or API gateway.</p>

<p>Resources in this layer can encroach deeply into the applications and infrastructure, making them difficult to manage.
The configuration may also end up located with the application UI, and not easily accessible via IaC config.</p>

<p>If there is a lot of investment in this area of configuration, it may be worth considering “pushing down” the configuration into lower layers so that it can be more easily managed and maintained.</p>

<h2 id="architectural-design-considerations">Architectural Design Considerations</h2>

<h3 id="security">Security</h3>

<p>Each layer has it’s own security considerations.
Typically for an application to be at all usable there needs to be some sort of information exchanged two it.
The challenge is balancing usability and security.</p>

<p>Management of the infrastructures security should follow <a href="https://web.archive.org/web/20210421022121/https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-207.pdf">Tenants of Zero Trust</a>:</p>

<ul>
  <li><strong>Data and compute are resources</strong>:  The raw data and compute are treated the same as application from a security perspective.  Properties like region and provider are aspects to be considered.</li>
  <li><strong>All communication is secured</strong>: All communication between data and compute should utilize secured communication at multiple layers with Wireguard tunnels, network policy, https etc.</li>
  <li><strong>Per-resource, per-session access</strong>: The trust boundary should be as close to the application as possible.</li>
  <li><strong>Determine access through collection of client and env properties</strong>: Raw credentials should obviously be considered, but additional bits of information can be used to make a more informed determination like source IP or session event timings.</li>
  <li><strong>Monitor the security</strong>: Red team yourself to constantly verify the protections in place are operational and effective.  In that vein, security pieces that do have issues should most often fail closed.</li>
  <li><strong>Security is a cycle of scanning, analysis, adjustment</strong>: Threats are constantly evolving, and your security needs to as well. Patching should automated to the point of approval to make it as frictionless as possible.</li>
  <li><strong>Use system state information to improve security posture</strong>: At a minimum all auth should be logged.  Collect what you will use.  While it may be inviting to mirror, decrypt and analyze complete netflow data, that quickly becomes cost-prohibitive.  There are sharp diminishing returns, so the volume should be calibrated to your security posture.</li>
</ul>

<h3 id="monitoring">Monitoring</h3>

<p>Unless there are regulatory or contract requirements, “lossy logging” can be an effective way to extract value from logs while minimizing the volume processed and stored.
High volume logging close to the source and pass on less information as the logs are transferred within the system.
As that data is replicated further from the source, funneling the data is important.
The amount of log traffic can be funneled/reduced by various means:</p>

<ul>
  <li>Minimize logging: Who needs debug?  Make it easy to enable, but if no ones looking then limiting logging to warning and above reduces log chatter</li>
  <li>Deduplication - Capture logs in a window only pass on changes in values.</li>
  <li>Summarization - Collect sets of logs and perform light</li>
  <li>Sampling - pass on a percentage of logs, simply dropping anything thats not passed on.  This is under tha assumption that the logs are not critical and can be lost without consequence.</li>
</ul>

<h3 id="source-of-truth">Source of Truth</h3>

<p>Idempotent definitions.
Take advantage and templating and configuration management to minimize the amount of manual intervention required.</p>

<p>There are innumerable methods to organize code, whether it a mono-repo, component repos, or monoliths.
The key is to choose one that fits your organization’s needs and stick with it.
What’s really important is that it is consistent and repeatable.</p>

<p>Configuration should be derived from the applications needs in a standard way to <strong>minimize snowflakes</strong>.
Code should be built on references to standard pieces instead of copies to minimize drift and decay.</p>

<p>Shared, application-level configurations should additive, allowing resources to selectively “opt-in” to sets of properties instead of being assigned a single, restrictive application type.
By defining the properties as sets, features from <a href="https://en.wikipedia.org/wiki/Set_(mathematics)#Basic_operations">set theory algebra</a> can be applied.</p>

<h3 id="lifecycle">Lifecycle</h3>

<p>All applications and resources will have a lifecycle scale and rate.
Like documentation, any resource deployed is almost immediately out of date.
Resources are in a constant state of flux making IaC a dynamic and evolving process.</p>

<p>While it is impossible to capture all aspects of a system as code, getting things “good enough” is good enough.
By virtue of being code, it can also be updated and improved over time.
Writing (describing?) resources is no different than writing regular business-logic-type code, there are just a lot more moving pieces.</p>

<ul>
  <li>Keep it organized - flatten abstractions behind configurable, sensible defaults</li>
  <li>Keep it simple - Minimize complex dependencies that cross resource boundaries.</li>
  <li>Use tools available - linting, perf analysis, and testing.</li>
  <li>Design for scale - Consider how the system will grow and adapt to change.</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<p>The easy part is deploying the service.
The application developer environment has changed greatly since even just a decade ago.
There are many more tools, libraries, and frameworks available to developers leading to more standard, predictable applications.
The advent of broader generative AI has made it easier to write code, but also more difficult to reason about the system as a whole.</p>

<p>Architecting the platform and integrations around the service is the real challenge.
The platform is like any other product, and needs to provide guarantees to the applications running on it.
It does not operate in a vacuum, it is a living system that needs to be maintained and adapted over time.
Like growing a tree, it takes care and patience to create a solid system.
Unlike a tree, everything is moldable, replicate-able, and roll-back-able (mostly).
By keeping the lifecycle of the resource in mind you can better anticipate and negate issues that may arise.</p>

<h2 id="further-reading">Further Reading</h2>

<ul>
  <li><a href="https://leebriggs.co.uk/blog/2023/08/17/structuring-iac">Lee Briggs - Structuring IaC</a></li>
  <li><a href="https://nathanpeck.com/rethinking-infrastructure-as-code-from-scratch/">Nathan Peck - Rethinking Infrastructure as Code from Scratch</a></li>
  <li><a href="https://cheatsheetseries.owasp.org/cheatsheets/Secure_Cloud_Architecture_Cheat_Sheet.html">OWASP - Cloud Architecture Cheatsheet</a></li>
  <li><a href="https://news.ycombinator.com/item?id=30904019">Hacker</a> <a href="https://news.ycombinator.com/item?id=36812848">News</a> <a href="https://news.ycombinator.com/item?id=19652376">Comments</a></li>
</ul>]]></content><author><name>Brooke Byers</name><email>ByersBr@pm.me</email></author><category term="[&quot;Programming&quot;]" /><summary type="html"><![CDATA[A layered model for describing Infrastructure as Code (IaC) at scale.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://b13rg.icecdn.tech//images/iac/bmc_software.jpg" /><media:content medium="image" url="https://b13rg.icecdn.tech//images/iac/bmc_software.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Enabling cloud-init on proxmox qcow2 images</title><link href="https://b13rg.icecdn.tech//proxmox-and-cloudinit/" rel="alternate" type="text/html" title="Enabling cloud-init on proxmox qcow2 images" /><published>2025-03-25T00:00:00+00:00</published><updated>2025-03-25T00:00:00+00:00</updated><id>https://b13rg.icecdn.tech//proxmox-and-cloudinit</id><content type="html" xml:base="https://b13rg.icecdn.tech//proxmox-and-cloudinit/"><![CDATA[<p>Proxmox is a valuable virtualization platform tham makes it simple to manage and deploy VMs.
Cloud-init is an “industry standard multi-distribution method for cross-platform cloud instance initialization”<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>.</p>

<p>While Proxmox itself does not support cloud-init out-of-the-box, there are ways to integrate it with your Proxmox environment. 
There are some extra steps to configure a QEMU image format<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>.</p>

<p>On you proxmox host, you will first need to install <code class="language-plaintext highlighter-rouge">libguestfs-tools</code> and the image you want to modify and use.
In this example, I get debian 12 generic cloud image.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>apt <span class="nb">install </span>libguestfs-tools
<span class="nb">mkdir</span> <span class="nt">-p</span> /var/lib/vz/template/qemu <span class="o">&amp;&amp;</span>  <span class="nb">cd</span> /var/lib/vz/template/qemu
wget https://cloud.debian.org/images/cloud/bookworm/20250316-2053//debian-12-genericcloud-amd64-20250316-2053.qcow2
</code></pre></div></div>

<p>Now that the image is downloaded, we need to patch it with <code class="language-plaintext highlighter-rouge">virt-edit</code> to add the cloud-init files.</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>virt-edit debian-12-genericcloud-amd64-20250316-2053.qcow2  /etc/cloud/cloud.cfg
virt-edit debian-12-genericcloud-amd64-20250316-2053.qcow2  /etc/ssh/sshd_config
virt-edit debian-12-genericcloud-amd64-20250316-2053.qcow2  /etc/motd
</code></pre></div></div>

<p>Now we will import the raw image into proxmox, and mark it as having cloud-init support:</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>qm importdisk 100 ./debian-12-genericcloud-amd64-20250316-2053.qcow2 local-lvm
qm <span class="nb">set </span>100 <span class="nt">--ide2</span> local-lvm:cloudinit
</code></pre></div></div>

<div class="footnotes" role="doc-endnotes">
  <ol>
    <li id="fn:1" role="doc-endnote">
      <p>https://cloudinit.readthedocs.io/en/latest/index.html <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
    <li id="fn:2" role="doc-endnote">
      <p>Proxmox Cloud-init docs: https://pve.proxmox.com/wiki/Cloud-Init_Support <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p>
    </li>
  </ol>
</div>]]></content><author><name>Brooke Byers</name><email>ByersBr@pm.me</email></author><category term="[&quot;Programing&quot;]" /><summary type="html"><![CDATA[A short guide on adding cloud-init options to an existing image.]]></summary></entry><entry><title type="html">Ternary Performance for the Modern Age</title><link href="https://b13rg.icecdn.tech//ternary-computing/" rel="alternate" type="text/html" title="Ternary Performance for the Modern Age" /><published>2024-12-09T00:00:00+00:00</published><updated>2024-12-09T00:00:00+00:00</updated><id>https://b13rg.icecdn.tech//ternary-computing</id><content type="html" xml:base="https://b13rg.icecdn.tech//ternary-computing/"><![CDATA[<p>wip</p>

<h2 id="ternary-computing">ternary computing</h2>

<p>Ternary computing uses 3 states instead of 2.
A ternary system is able to store information more densely than a binary due to its increased state capacity. 
This density allows for more efficient data handling and potentially higher performance in specific applications compared to binary systems. 
However, it also introduces new challenges such as the need for specialized hardware and algorithms designed to operate within the ternary framework.</p>

<p>Ternary logic has performance benefits for certain types of computations like matrix multiplication.</p>

<p>Balanced ternary computing uses three states (-1, 0, +1) instead of two (0, 1).
Unbalanced ternary (0, 1, 2) could also be applied to some applications.
These systems allow a value to be represented multiple ways, e.g. 3 can be represented as (1, 0, -1) or (0, 1, 1).
The encoding of the value allows certain computations to be performed more efficiently</p>

<p>Modern ternary-native hardware: https://www.ternary-computing.com/history/CPU-History.html</p>

<h2 id="ternary-llms">ternary llms</h2>

<p>1-bit llms show better efficiency in terms of speed and energy use compared the usual fp16 type format. <a href="https://arxiv.org/html/2410.16144v2">1-bit AI Infra: Part 1.1, Fast and Lossless BitNet b1.58 Inference on CPUs </a>.</p>

<p>1-bit model architectures can very cleanly be mapped onto ternary logic, which can in turn be implemented using FPGAs.</p>

<p>To integrate with existing binary-native hardware, a hybrid approach could be used where the ternary logic is implemented on FPGAs using 2 bits that provide the normal (-1, 0, +1) values along with a hardware “null” or special purpose flag value.</p>

<h2 id="architecting-hardware-to-data">architecting hardware to data</h2>

<p>Goals:</p>

<ul>
  <li>move calculations as deep into hardware as possible for performance and power efficiency</li>
  <li>moldable - have hardware adapt to required computations</li>
</ul>

<p>Use fpga to encode model weights directly into hardware, allowing for efficient computation and storage.
Limited by fpga density and llm model size.
With onboard memory caching, the fpga could reconfigure itself for each stage of model evaluation.</p>

<p>FPGAs are computationally efficient, but not as space efficient as harder hardware.
Adopting a blade server type form factor would increase compute density.</p>

<p>Use pcie lanes to connect multiple fpga cards to each other, and spread model layers across cards.
This would allow for a distributed system where each card handles a different stage of model evaluation for scalability.
Management could be performed by separate attached compute ( a la <a href="https://www.jeffgeerling.com/blog/2023/testing-pcie-on-raspberry-pi-5">raspberry pi</a>) or on the FPGAs themselves (FPGA-netes, F9s? ).</p>]]></content><author><name>Brooke Byers</name><email>ByersBr@pm.me</email></author><category term="[&quot;Programming&quot;]" /><summary type="html"><![CDATA[Using ternary to improve computing performance]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://b13rg.icecdn.tech//public/images/linkers/regular/last.gif" /><media:content medium="image" url="https://b13rg.icecdn.tech//public/images/linkers/regular/last.gif" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Wordpress acf -&amp;gt; scf attack</title><link href="https://b13rg.icecdn.tech//wordpress-acf-plugin/" rel="alternate" type="text/html" title="Wordpress acf -&amp;gt; scf attack" /><published>2024-10-13T00:00:00+00:00</published><updated>2024-10-13T00:00:00+00:00</updated><id>https://b13rg.icecdn.tech//wordpress-acf-plugin</id><content type="html" xml:base="https://b13rg.icecdn.tech//wordpress-acf-plugin/"><![CDATA[<!-- Image example
![MS-DOS Family Tree](/images/folder/filename.png){:width="700px"}
-->
<!-- Link example -->

<p>Good summary of events with links: https://anderegg.ca/2024/10/13/acf-has-been-hijacked</p>

<ul>
  <li>Guideline invoked: https://github.com/wordpress/wporg-plugin-guidelines/blob/trunk/guideline-18.md</li>
  <li>https://x.com/wp_acf/status/1841843084700598355</li>
</ul>

<table>
  <thead>
    <tr>
      <th>Date</th>
      <th>Notes</th>
      <th>Links</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Aug. 28</td>
      <td>ACF <code class="language-plaintext highlighter-rouge">6.3.6</code> released</td>
      <td><a href="https://github.com/AdvancedCustomFields/acf/releases/tag/6.3.6">link</a></td>
    </tr>
    <tr>
      <td>Oct. 2/3</td>
      <td>ACF <code class="language-plaintext highlighter-rouge">6.3.7</code> released.  Changelog: <code class="language-plaintext highlighter-rouge">Security - ACF Free now uses its own update mechanism from WP Engine servers</code></td>
      <td><a href="https://github.com/AdvancedCustomFields/acf/releases/tag/6.3.7">link</a></td>
    </tr>
    <tr>
      <td>Oct. 7</td>
      <td>ACF <code class="language-plaintext highlighter-rouge">6.3.8</code>.  Changelog: <code class="language-plaintext highlighter-rouge">Security - ACF defined Post Type and Taxonomy metabox callbacks no longer have access to $_POST data. (Thanks to the Automattic Security Team for the disclosure)</code></td>
      <td><a href="https://github.com/AdvancedCustomFields/acf/releases/tag/6.3.8">link</a></td>
    </tr>
    <tr>
      <td>Oct. 12 17:47</td>
      <td>ACF becomes SCF</td>
      <td><a href="https://wordpress.org/plugins/advanced-custom-fields/">Zip file metadata</a></td>
    </tr>
    <tr>
      <td>Oct. 12 18:26</td>
      <td>Wordpress announcement of taking over acf: <a href="https://wordpress.org/news/2024/10/secure-custom-fields/">blog post</a></td>
      <td> </td>
    </tr>
  </tbody>
</table>

<p><img src="files/acf-scf/acf-scf-2024-10-12-17-27.png" alt="screenshot of now secure custom fields plugin" /></p>

<p>Downloaded version <code class="language-plaintext highlighter-rouge">6.3.6</code> of advanced custom fields from github: <a href="https://github.com/AdvancedCustomFields/acf/releases/tag/6.3.6">[AdvancedCustomFields/acf](https://github.com/AdvancedCustomFields/acf/releases/tag/6.3.6)</a>
Downloaded version <code class="language-plaintext highlighter-rouge">6.3.6.2</code> of secure custom fields from wordpress: <a href="https://wordpress.org/plugins/advanced-custom-fields/">https://wordpress.org/plugins/advanced-custom-fields/</a></p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;</span> find <span class="nb">.</span> <span class="nt">-name</span> <span class="s1">'*.zip'</span>  <span class="nt">-exec</span> <span class="nb">sha256sum</span> <span class="o">{}</span> <span class="se">\;</span>
1403316f569fdb8947dbb0ef6f6f0f154678fe978e46ccc06571d71918c685dd  ./advanced-custom-fields.6.3.6.2.zip
91cd88e29e1c220603e17fdfc267943a16e297a87d24325eafc15794364a0a83  ./advanced-custom-fields-6.3.6.zip
</code></pre></div></div>

<p><a href="files/acf-scf/diff.txt">Complete txt diff</a>
<a href="files/acf-scf/diff-summary.txt">File list txt diff</a></p>

<details>
  <summary>List of changed files between the two plugins</summary>

<div>

    <p>The metadata for the Advanced Custom Fields plugin has been updated along with the package</p>

    <div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>diff <span class="nt">-qr</span> ./advanced-custom-fields-6.3.6/ ./advanced-custom-fields-6.3.6.2/
Files ./advanced-custom-fields-6.3.6/acf.php and ./advanced-custom-fields-6.3.6.2/acf.php differ
Files ./advanced-custom-fields-6.3.6/includes/acf-bidirectional-functions.php and ./advanced-custom-fields-6.3.6.2/includes/acf-bidirectional-functions.php differ
Files ./advanced-custom-fields-6.3.6/includes/acf-helper-functions.php and ./advanced-custom-fields-6.3.6.2/includes/acf-helper-functions.php differ
Files ./advanced-custom-fields-6.3.6/includes/acf-value-functions.php and ./advanced-custom-fields-6.3.6.2/includes/acf-value-functions.php differ
Files ./advanced-custom-fields-6.3.6/includes/admin/admin-internal-post-type-list.php and ./advanced-custom-fields-6.3.6.2/includes/admin/admin-internal-post-type-list.php differ
Files ./advanced-custom-fields-6.3.6/includes/admin/admin.php and ./advanced-custom-fields-6.3.6.2/includes/admin/admin.php differ
Files ./advanced-custom-fields-6.3.6/includes/admin/post-types/admin-field-group.php and ./advanced-custom-fields-6.3.6.2/includes/admin/post-types/admin-field-group.php differ
Files ./advanced-custom-fields-6.3.6/includes/admin/views/acf-field-group/field.php and ./advanced-custom-fields-6.3.6.2/includes/admin/views/acf-field-group/field.php differ
Files ./advanced-custom-fields-6.3.6/includes/admin/views/global/header.php and ./advanced-custom-fields-6.3.6.2/includes/admin/views/global/header.php differ
Files ./advanced-custom-fields-6.3.6/includes/admin/views/global/navigation.php and ./advanced-custom-fields-6.3.6.2/includes/admin/views/global/navigation.php differ
Files ./advanced-custom-fields-6.3.6/includes/admin/views/tools/tools.php and ./advanced-custom-fields-6.3.6.2/includes/admin/views/tools/tools.php differ
Files ./advanced-custom-fields-6.3.6/includes/assets.php and ./advanced-custom-fields-6.3.6.2/includes/assets.php differ
Files ./advanced-custom-fields-6.3.6/includes/class-acf-site-health.php and ./advanced-custom-fields-6.3.6.2/includes/class-acf-site-health.php differ
Files ./advanced-custom-fields-6.3.6/includes/fields.php and ./advanced-custom-fields-6.3.6.2/includes/fields.php differ
Files ./advanced-custom-fields-6.3.6/includes/post-types/class-acf-post-type.php and ./advanced-custom-fields-6.3.6.2/includes/post-types/class-acf-post-type.php differ
Files ./advanced-custom-fields-6.3.6/includes/post-types/class-acf-taxonomy.php and ./advanced-custom-fields-6.3.6.2/includes/post-types/class-acf-taxonomy.php differ
Files ./advanced-custom-fields-6.3.6/README.md and ./advanced-custom-fields-6.3.6.2/README.md differ
Files ./advanced-custom-fields-6.3.6/readme.txt and ./advanced-custom-fields-6.3.6.2/readme.txt differ
</code></pre></div>    </div>
  </div>
</details>

<details>
  <summary>Plugin Manifest changes</summary>

<div>

    <div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gh">diff --color -u --suppress-common-lines -b -r ./advanced-custom-fields-6.3.6/acf.php ./advanced-custom-fields-6.3.6.2/acf.php
</span><span class="gd">--- ./advanced-custom-fields-6.3.6/acf.php	2024-08-28 07:35:13.000000000 -0700
</span><span class="gi">+++ ./advanced-custom-fields-6.3.6.2/acf.php	2024-10-12 17:25:59.560336291 -0700
</span><span class="p">@@ -1,17 +1,13 @@</span>
 &lt;?php
 /**
<span class="gd">- * Advanced Custom Fields
</span><span class="gi">+ * Secure Custom Fields
</span>  *
<span class="gd">- * @package       ACF
- * @author        WP Engine
- *
- * @wordpress-plugin
- * Plugin Name:       Advanced Custom Fields
- * Plugin URI:        https://www.advancedcustomfields.com
- * Description:       Customize WordPress with powerful, professional and intuitive fields.
- * Version:           6.3.6
- * Author:            WP Engine
- * Author URI:        https://wpengine.com/?utm_source=wordpress.org&amp;utm_medium=referral&amp;utm_campaign=plugin_directory&amp;utm_content=advanced_custom_fields
</span><span class="gi">+ * Plugin Name:       Secure Custom Fields
+ * Plugin URI:        http://wordpress.org/plugins/advanced-custom-fields/
+ * Description:       Secure Custom Fields is a fork of the Advanced Custom Fields plugin, which will be maintained by WordPress.org, for security and functionality updates.
+ * Version:           6.3.6.2
+ * Author:            WordPress.org
+ * Author URI:        https://wordpress.org/
</span>  * Text Domain:       acf
  * Domain Path:       /lang
  * Requires PHP:      7.4
</code></pre></div>    </div>
  </div>
</details>

<h2 id="notes-about-changes">Notes about changes</h2>

<ul>
  <li>Renames plugin fields but not the zip file</li>
  <li>Still points to https://wordpress.org/plugins/advanced-custom-fields/ , https://wordpress.org/plugins/secure-custom-fields/ doesn’t exist</li>
  <li>pointed support to https://wordpress.org/support/plugin/advanced-custom-fields/</li>
  <li>disables+removes acf pro functions</li>
  <li>removes references to documentation, and help tab</li>
  <li>added code from <code class="language-plaintext highlighter-rouge">6.3.8</code>: <code class="language-plaintext highlighter-rouge">Ensure the metabox being called does not perform any unsafe operations</code></li>
  <li>adjust readme:
    <ul>
      <li>ACF -&gt; SCF</li>
    </ul>
  </li>
</ul>

<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gd">-ACF helps customize WordPress with powerful, professional and intuitive fields. Proudly powering over 2 million sites, WordPress developers love ACF.
</span><span class="gi">+Secure Custom Fields is a free fork of the Advanced Custom Fields plugin created originally for security updates, but now includes functionality improvements to make this plugin non-commercial in the plugin directory. If you'd like to get involved, submit some code! We want the 2M+ sites that will receive this update to have the best code and functionality possible.
</span></code></pre></div></div>

<details>
  <summary>Changelog Replacements</summary>
  
<div>

    <div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gi">+= 6.3.6.2 =
+*Release Date 12th October 2024*
+* Security - Harden fix in 6.3.6.1 to cover $_REQUEST as well.
+* Fork - Change name of plugin to Secure Custom Fields.
</span> 
<span class="gd">-[View the full changelog](https://www.advancedcustomfields.com/changelog/)
</span><span class="gi">+= 6.3.6.1 =
+*Release Date 7th October 2024*
</span> 
<span class="gd">-== Upgrade Notice ==
</span><span class="err">\</span> No newline at end of file
<span class="gi">+* Security - ACF defined Post Type and Taxonomy metabox callbacks no longer have access to $_POST data. (Thanks to the Automattic Security Team for the disclosure)
</span></code></pre></div>    </div>
  </div>
</details>

<details>
  <summary>Latest [Relevant] Release notes</summary>
<div>
    <p>Release notes for 6.3.8, from https://github.com/AdvancedCustomFields/acf/blob/6.3.8/readme.txt :</p>

    <div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gd">&lt;code&gt;
</span><span class="gh">== Changelog ==
</span>
= 6.3.8 =
<span class="err">*Release</span> Date 7th October 2024*

* Security - ACF defined Post Type and Taxonomy metabox callbacks no longer have access to $_POST data. (Thanks to the Automattic Security Team for the disclosure)

= 6.3.7 =
<span class="err">*Release</span> Date 2nd October 2024*

* Security - ACF Free now uses its own update mechanism from WP Engine servers

= 6.3.6 =
<span class="err">*Release</span> Date 28th August 2024*

* Security - Newly added fields now have to be explicitly set to allow access in the content editor (when using the ACF shortcode or Block Bindings) to increase the security around field permissions. [See the release notes for more details](https://www.advancedcustomfields.com/blog/acf-6-3-6/#field-value-access-editor)
<span class="err">*</span> Security Fix - Field labels are now correctly escaped when rendered in the Field Group editor, to prevent a potential XSS issue. Thanks to Ryo Sotoyama of Mitsui Bussan Secure Directions, Inc. for the responsible disclosure
<span class="err">*</span> Fix - Validation and Block AJAX requests nonces will no longer be overridden by third party plugins
<span class="err">*</span> Fix - Detection of third party select2 libraries will now default to v4 rather than v3
<span class="err">*</span> Fix - Block previews will now display an error if the render template PHP file is not found
</code></pre></div>    </div>
  </div>
</details>

<p>The security fix in question is related to how <code class="language-plaintext highlighter-rouge">advanced-custom-fields</code> processes GET and POST requests.
Files:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">includes/post-types/class-acf-post-type.php</code></li>
  <li><code class="language-plaintext highlighter-rouge">/includes/post-types/class-acf-taxonomy.php</code></li>
</ul>

<p>The first fix, clearing POST parameters, was added as part of <a href="https://github.com/AdvancedCustomFields/acf/commit/c6b165369a85bd25136bc75095acd46fe8a45b05">acf 6.3.8</a></p>

<p>Wordpress’s fork took that fix and added clearing the GET parameters as part of it’s first release <code class="language-plaintext highlighter-rouge">6.3.6.2</code>.
This didn’t incorporate the switch to pointing to</p>

<p>. despite the changelog referenceing <code class="language-plaintext highlighter-rouge">POST</code> data it looks like the change was securing <code class="language-plaintext highlighter-rouge">GET</code> requests.
Since it uses code from <code class="language-plaintext highlighter-rouge">6.3.8</code>, it must have been available before the “fork” took place.
Instead sort of minor update? <code class="language-plaintext highlighter-rouge">6.3.6</code> -&gt; <code class="language-plaintext highlighter-rouge">6.3.6.2</code>.</p>

<details>
  <summary>Comparing fix to referenced `6.3.8` from acf</summary>


<div>
    <div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="gi">&gt; \diff -u --suppress-common-lines -b ./class-acf-post-type-6.3.8.php ./advanced-custom-fields-6.3.6.2/includes/post-types/class-acf-post-type.php
</span><span class="gd">--- ./class-acf-post-type-6.3.8.php     2024-10-12 19:26:56.058355194 -0700
</span><span class="gi">+++ ./advanced-custom-fields-6.3.6.2/includes/post-types/class-acf-post-type.php        2024-10-12 17:25:59.704335853 -0700
</span><span class="p">@@ -649,12 +649,15 @@</span>
                        }
 
                        $original_post = $_POST; //phpcs:ignore -- Only used as temporary storage to prevent CSRFs in callbacks.
<span class="gi">+                       $original_request = $_REQUEST;
</span>                        $_POST         = array();
<span class="gi">+                       $_REQUEST         = array();
</span>                        $return        = false;
                        if ( is_callable( $original_cb ) ) {
                                $return = call_user_func( $original_cb, $post );
                        }
                        $_POST = $original_post;
<span class="gi">+                       $_REQUEST = $original_request;
</span>                        return $return;
                }
</code></pre></div>    </div>
  </div>
</details>

<p>The fix added is also clearing the <code class="language-plaintext highlighter-rouge">GET</code> parameters from the original request before calling the meta box callback function specified.</p>

<p>Before calling the function, it grabs it from</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code>
<span class="nv">$original_cb</span>  <span class="o">=</span> <span class="k">isset</span><span class="p">(</span> <span class="nv">$acf_taxonomy</span><span class="p">[</span><span class="s1">'meta_box_cb'</span><span class="p">]</span> <span class="p">)</span> <span class="o">?</span> <span class="nv">$acf_taxonomy</span><span class="p">[</span><span class="s1">'meta_box_cb'</span><span class="p">]</span> <span class="o">:</span> <span class="kc">false</span><span class="p">;</span>

<span class="c1">// Prevent access to any wp_ prefixed functions in a callback.</span>
<span class="k">if</span> <span class="p">(</span> <span class="nf">apply_filters</span><span class="p">(</span> <span class="s1">'acf/taxonomy/prevent_access_to_wp_functions_in_meta_box_cb'</span><span class="p">,</span> <span class="kc">true</span> <span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nb">substr</span><span class="p">(</span> <span class="nb">strtolower</span><span class="p">(</span> <span class="nv">$original_cb</span> <span class="p">),</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">3</span> <span class="p">)</span> <span class="o">===</span> <span class="s1">'wp_'</span> <span class="p">)</span> <span class="p">{</span>
  <span class="c1">// Don't execute register meta box callbacks if an internal wp function by default.</span>
  <span class="k">return</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>

<p>Stops attacker from being able to pass extra GET parameters to meta box callback functions when setting it for a post.
To be able to call this a user must be able to register acf-taxonomy custom post types within WordPress.</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// WordPress defaults to the tags/categories metabox, but a custom callback or `false` is also supported.</span>
			<span class="nv">$meta_box</span> <span class="o">=</span> <span class="k">isset</span><span class="p">(</span> <span class="nv">$post</span><span class="p">[</span><span class="s1">'meta_box'</span><span class="p">]</span> <span class="p">)</span> <span class="o">?</span> <span class="p">(</span><span class="n">string</span><span class="p">)</span> <span class="nv">$post</span><span class="p">[</span><span class="s1">'meta_box'</span><span class="p">]</span> <span class="o">:</span> <span class="s1">'default'</span><span class="p">;</span>
</code></pre></div></div>

<p>Internally, <code class="language-plaintext highlighter-rouge">acf</code> is still used in most places.</p>]]></content><author><name>Brooke Byers</name><email>ByersBr@pm.me</email></author><category term="[&quot;Programming&quot;]" /><summary type="html"><![CDATA[An file analysis of the acf wordpress plugin takeover]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://b13rg.icecdn.tech//public/images/buttons/large/ahmygod.gif" /><media:content medium="image" url="https://b13rg.icecdn.tech//public/images/buttons/large/ahmygod.gif" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>