<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Future</title>
    <description>The most recent home feed on Future.</description>
    <link>https://future.forem.com</link>
    <atom:link rel="self" type="application/rss+xml" href="https://future.forem.com/feed"/>
    <language>en</language>
    <item>
      <title>The Real Token Economy Is Not About Spending Less. It Is About Thinking Smaller.</title>
      <dc:creator>marcosomma</dc:creator>
      <pubDate>Sun, 26 Apr 2026 22:46:01 +0000</pubDate>
      <link>https://future.forem.com/marcosomma/the-real-token-economy-is-not-about-spending-less-it-is-about-thinking-smaller-3j3e</link>
      <guid>https://future.forem.com/marcosomma/the-real-token-economy-is-not-about-spending-less-it-is-about-thinking-smaller-3j3e</guid>
      <description>&lt;p&gt;I saw a video today that made me laugh, then made me a bit worried.&lt;/p&gt;

&lt;p&gt;It was one of those jokes that is not really a joke because you can already see some company doing it six months from now. A manager was basically complaining because an employee was not spending enough AI tokens. Not enough tokens. As if tokens were steps on a fitness tracker.&lt;/p&gt;

&lt;p&gt;"You only burned 2,000 tokens today, Susan. Are you even working?"&lt;/p&gt;

&lt;p&gt;It sounds absurd, but we are not that far from it. Companies are already starting to measure AI adoption through number of prompts, number of tool calls, input tokens, output tokens, cost per user, cost per team, cost per workflow. And to be clear, I do not think this is automatically wrong. Measuring token usage makes sense. Tokens are cost. Tokens are latency. Tokens are context. They are also a trace of how people and systems are using AI.&lt;/p&gt;

&lt;p&gt;The problem starts when we confuse the metric with the objective. We did this with hours worked. We did this with tickets closed. We did this with meetings attended. We did this with leads, where 1,000 unqualified leads looked better than 10 serious conversations because the spreadsheet was having a great day and nobody wanted to ruin the mood with reality.&lt;/p&gt;

&lt;p&gt;Now we risk doing the same with tokens.&lt;/p&gt;

&lt;p&gt;More tokens does not mean better work. Fewer tokens does not mean smarter work. The interesting signal is not the raw number. The interesting signal is the relationship between what you put into the model, what you ask it to do, and what comes out. That is where I think the real token economy starts. Not as a cost saving obsession, but as an architectural signal.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tokens are not just money
&lt;/h2&gt;

&lt;p&gt;The first way people talk about tokens is cost, and that is understandable. If you use hosted LLM APIs, tokens map quite directly to money. Input tokens cost something. Output tokens cost something. Larger models cost more. Long contexts cost more. Retries cost more. Bad prompts cost more. Bad architecture costs a lot more, but usually in a way that arrives later and looks like a reliability problem.&lt;/p&gt;

&lt;p&gt;So the first instinct is to optimize token consumption. Compress prompts. Summarize context. Pick cheaper models. Cache responses. Reduce unnecessary output. All of that is useful, but I think it is only the shallow layer of the problem.&lt;/p&gt;

&lt;p&gt;The more interesting question is not "how many tokens did this task consume?" The more interesting question is "what cognitive operation did those tokens represent?"&lt;/p&gt;

&lt;p&gt;Because input tokens and output tokens are not the same thing. Input tokens usually buy context. They are the material you ask the model to look at. Output tokens usually buy generation, explanation, structure, synthesis, or action. If I send 10,000 input tokens to a model and get back 10 output tokens, that could be terrible. It could also be exactly right.&lt;/p&gt;

&lt;p&gt;If the task is to read a long error log and return whether the failure is caused by authentication, a tiny output may be valid. If the task is to classify a product review as positive, neutral, or negative, a small answer is not a failure. It is the point. If the task is to route a bug report to the correct engineering queue, I do not need a novel. I need the right route.&lt;/p&gt;

&lt;p&gt;So no, high input and low output is not automatically bad. But it is a signal. And I think that signal deserves a lot more attention than it currently gets.&lt;/p&gt;

&lt;h2&gt;
  
  
  Balance does not mean symmetry
&lt;/h2&gt;

&lt;p&gt;When I talk about token balance, I do not mean that input tokens and output tokens should be equal. That would be a very silly metric, and we already have enough silly metrics trying to cosplay as management science.&lt;/p&gt;

&lt;p&gt;By balance, I mean the relationship between the size of the input, the size of the output, and the value of the decision produced. A large input with a tiny output usually means the model is doing some kind of compression, classification, extraction, routing, filtering, moderation, scoring, validation, or decision making. A small input with a large output usually means the model is doing generation, expansion, explanation, drafting, or ideation. A large input with a large output usually means synthesis, transformation, summarization, comparison, or multi-step reasoning. A small input with a small output is usually a narrow atomic task.&lt;/p&gt;

&lt;p&gt;None of these patterns are good or bad by themselves. They tell you something about the shape of the work. And sometimes the shape of the work is screaming.&lt;/p&gt;

&lt;p&gt;Imagine you send a giant prompt containing a full meeting transcript, a product description, usage logs, a bug report, five examples, a JSON schema, tone guidelines, safety instructions, and a final line saying "be concise" because apparently we enjoy irony. Then you ask the model to return this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priority"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"high"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Maybe that is fine. Maybe the classification really required all of that context. But maybe you just built a cognitive washing machine to clean one spoon.&lt;/p&gt;

&lt;p&gt;The point is not that the token ratio is wrong. The point is that the ratio invites questions. Did the task need all of that context? Could the context have been retrieved more narrowly? Could the classification have been separated from the extraction? Could a smaller model do part of the work? Could a deterministic rule do part of it? Could the final output be validated separately instead of trusting one giant model call?&lt;/p&gt;

&lt;p&gt;That is where token metrics become useful. Not as a scoreboard. As a diagnostic tool.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real problem is overloaded cognition
&lt;/h2&gt;

&lt;p&gt;A lot of AI workflows are not expensive because the model is expensive. They are expensive because the task design is confused. We ask one model call to do too many things at once, then we act surprised when the model behaves like a very intelligent intern who received eight contradictory Jira tickets in one message.&lt;/p&gt;

&lt;p&gt;Read this long input. Understand the domain. Extract twenty fields. Normalize them. Infer missing values. Respect the schema. Apply business rules. Avoid hallucinations. Explain your decision. Be concise. Be deterministic. Also, please do it in one call because we saw a demo once and now we think architecture is a prompt template.&lt;/p&gt;

&lt;p&gt;This is where things become fragile. One big prompt. One big model. One fragile JSON output. One retry loop when it fails. One annoyed engineer staring at a malformed comma at 1:12 AM wondering why they studied data structures.&lt;/p&gt;

&lt;p&gt;The problem is not only cost. The problem is that the reasoning surface is too large. Every additional instruction increases the model's degrees of freedom. Every unrelated piece of context adds noise. Every extra output field increases the chance of format drift. Every hidden dependency between fields makes validation harder. And when the output fails, you often do not know why.&lt;/p&gt;

&lt;p&gt;Was the context too long? Was the instruction ambiguous? Was the schema too complex? Was the task logically overloaded? Was the model too weak? Was the model too creative? Was Mercury in retrograde? At some point, debugging a giant prompt starts to feel like debugging a dream.&lt;/p&gt;

&lt;p&gt;This is why I think the unit of optimization should not be the prompt. The unit of optimization should be the cognitive task.&lt;/p&gt;

&lt;h2&gt;
  
  
  Think smaller, not just cheaper
&lt;/h2&gt;

&lt;p&gt;When people hear "token economy", they often think about saving money. I think that is incomplete. The better version is this: design AI workflows so each model call has the smallest reasonable cognitive surface.&lt;/p&gt;

&lt;p&gt;Not the smallest prompt. Not the cheapest model. The smallest cognitive surface.&lt;/p&gt;

&lt;p&gt;A task has a cognitive surface when it asks the model to consider a certain amount of context, make a certain type of judgment, and produce a certain kind of output. A wide cognitive surface is something like this: read a conversation, infer the user's emotional state, detect all action items, classify the sales opportunity, extract objections, score urgency, summarize the call, generate a follow-up email, and return a perfect JSON object with 28 fields.&lt;/p&gt;

&lt;p&gt;That is not one task. That is a small village.&lt;/p&gt;

&lt;p&gt;A narrower cognitive task is different. Given this segment of a product feedback thread, identify whether the user mentions pricing as a blocker. Return true or false. Or extract only the next meeting date from this text and return null if absent. Or given these three already extracted signals, choose the priority level from low, medium, or high.&lt;/p&gt;

&lt;p&gt;Those tasks have narrower inputs and narrower outputs. They are easier to validate. They are easier to retry. They can often run on smaller models. Some can be replaced by deterministic code. Most importantly, they reduce ambiguity.&lt;/p&gt;

&lt;p&gt;This is the part that matters. The best token optimization is not always compression. Sometimes the best token optimization is decomposition.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 20 field JSON problem
&lt;/h2&gt;

&lt;p&gt;Let us take a simple example. You have a large input document and you need a structured output with 20 values. The obvious modern AI approach is to send the full document to a model and ask it to extract everything in one JSON object. Add a schema, add "do not hallucinate", add "use null when unknown", maybe add three examples, and hope the model behaves.&lt;/p&gt;

&lt;p&gt;Sometimes this works. Sometimes it works very well in the demo. Then production arrives, wearing boots.&lt;/p&gt;

&lt;p&gt;The model misses a field. It invents a value. It mixes two fields. It returns invalid JSON. It follows the schema but puts the wrong value in the right place, which is worse because it looks correct. It explains itself inside a field because apparently JSON needed feelings.&lt;/p&gt;

&lt;p&gt;So you add more instructions. Then stricter schema language. Then validation. Then retry. Then a stronger model. Then a more expensive model. Then someone says, "Maybe we should fine-tune it." And now your simple extraction pipeline has become a small national infrastructure project.&lt;/p&gt;

&lt;p&gt;A different approach is to ask a boring but useful question: are these 20 values actually one cognitive task?&lt;/p&gt;

&lt;p&gt;Maybe not. Maybe five fields are direct extraction. Maybe three require classification. Maybe four depend on dates. Maybe two require numerical normalization. Maybe six are only relevant if a previous condition is true. In that case, one big prompt is not simpler. It is only hiding the complexity inside the model call.&lt;/p&gt;

&lt;p&gt;You may get a better system by clustering the fields by semantic dependency. For example, direct identifiers can be one batch. Dates and temporal constraints can be another. Risk indicators can be another. Obligations and responsible parties can be another. The final normalized summary can be built only after the previous signals exist.&lt;/p&gt;

&lt;p&gt;Each batch can have a smaller prompt, a smaller schema, and a narrower validation rule. Some batches may not need an LLM. Some can use regex, parsers, lookup tables, embeddings, or deterministic checks. Some can use a small local model. Only the genuinely difficult parts need the expensive model.&lt;/p&gt;

&lt;p&gt;This is where the cost savings come from, but cost is only one part of the win. You also get better observability. If the final output is wrong, you can inspect which subtask failed. You can measure field-level accuracy. You can retry only the failing part. You can swap models for one stage without touching the rest. You can cache intermediate outputs. You can add deterministic validation at the boundary.&lt;/p&gt;

&lt;p&gt;That is a real token economy. Not "use fewer tokens". Spend tokens where cognition is actually needed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Smaller prompts reduce variance
&lt;/h2&gt;

&lt;p&gt;I want to be careful with the word deterministic. LLMs are not truly deterministic systems in the classical engineering sense, even when you reduce temperature and constrain output. They are probabilistic systems. But workflow design can make their behavior more stable, more reproducible, and more controllable.&lt;/p&gt;

&lt;p&gt;Smaller prompts with narrower objectives usually reduce the degrees of freedom of the model. If the model has one job, a small output space, and a strict schema, there are fewer ways to fail. If the model has twenty jobs, a large input, competing instructions, implicit dependencies, and a complex schema, you should not be surprised when it occasionally decides to express itself like a haunted spreadsheet.&lt;/p&gt;

&lt;p&gt;This is why task decomposition can improve consistency. Not because small calls magically make the model deterministic, but because small calls make the system around the model easier to control. The output space is narrower. The validation is simpler. The retry logic is cheaper. The failure modes are easier to classify. The model choice becomes more flexible. The prompts become easier to test.&lt;/p&gt;

&lt;p&gt;And the orchestration becomes explicit.&lt;/p&gt;

&lt;p&gt;That last point matters a lot. When everything happens inside one prompt, the process is invisible. When you split the work into stages, the process becomes inspectable. This is the difference between hoping the model thinks correctly and designing a system where each step can be observed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where OrKa fits into this
&lt;/h2&gt;

&lt;p&gt;This is one of the reasons I have been building &lt;a href="https://orkacore.com" rel="noopener noreferrer"&gt;OrKa&lt;/a&gt;, an orchestration framework for AI agents and reasoning workflows. The point of OrKa is not "use more agents because agents are cool". Honestly, if adding agents makes your system less understandable, congratulations, you have invented distributed confusion.&lt;/p&gt;

&lt;p&gt;The point is different. Make cognitive work explicit. Define the flow. Split reasoning into smaller units. Route tasks. Log execution. Validate outputs. Keep memory and context under control. Make the system inspectable instead of praying over a large prompt.&lt;/p&gt;

&lt;p&gt;In this view, an LLM is not the application. It is one component inside a system. Sometimes the LLM extracts. Sometimes it classifies. Sometimes it rewrites. Sometimes it evaluates. Sometimes it should not be called at all. The orchestration layer decides how work moves between these pieces.&lt;/p&gt;

&lt;p&gt;That is where token economy becomes architecture. You are no longer asking only how to reduce a prompt by 20 percent. You are asking which cognitive step actually needs this context.&lt;/p&gt;

&lt;p&gt;That question changes everything. Maybe the first model call only needs the user message. Maybe the second needs the relevant log snippet. Maybe the third needs only three extracted fields. Maybe the final formatter needs no model at all. If you send the full context to every step, you are not designing an AI system. You are photocopying the universe and asking a model to find the invoice number.&lt;/p&gt;

&lt;p&gt;It may work. It is not a strategy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Token metrics should trigger questions
&lt;/h2&gt;

&lt;p&gt;So how should teams use token metrics? Not as productivity surveillance. Not as a way to shame people for using too many or too few tokens. Not as a leaderboard where the person with the most prompts wins some cursed office trophy.&lt;/p&gt;

&lt;p&gt;Token metrics should trigger engineering questions.&lt;/p&gt;

&lt;p&gt;When input tokens are very high and output tokens are very low, ask whether the task is intentionally compressive or accidentally overloaded. When output tokens are very high, ask whether the model is generating useful structure or just producing expensive fog. When the same context is repeatedly sent across multiple calls, ask whether retrieval, caching, or state passing could reduce duplication. When a large model is used for simple extraction, ask whether a smaller model or deterministic rule would work. When retries consume a lot of tokens, ask whether the schema, validation, or task boundaries are wrong.&lt;/p&gt;

&lt;p&gt;This does not mean splitting tasks is automatically better. If you send the same 10,000 token input twenty times to extract twenty fields, you may have made the system more expensive and slower. You have not built architecture. You have built a very complicated way to duplicate context.&lt;/p&gt;

&lt;p&gt;The win comes when decomposition is paired with context narrowing. Extract the relevant segment once. Reuse intermediate state. Cluster fields that share dependencies. Route only the necessary context. Validate locally. Use smaller models where possible. Stop calling the model when code can do the job.&lt;/p&gt;

&lt;p&gt;This is not anti-LLM. It is pro-system.&lt;/p&gt;

&lt;h2&gt;
  
  
  A simple mental model
&lt;/h2&gt;

&lt;p&gt;Here is the mental model I keep coming back to. Input tokens are attention budget. Output tokens are commitment surface.&lt;/p&gt;

&lt;p&gt;The more input you provide, the more the model has to attend to. The more output you request, the more opportunities the model has to drift. A workflow becomes more stable when the attention budget and the commitment surface are aligned with the actual cognitive task.&lt;/p&gt;

&lt;p&gt;If the model needs to classify one thing, do not ask it to also summarize, extract, explain, normalize, and format a complex object. If the model needs to generate a long answer, do not overload it with irrelevant context that only increases noise. If the model needs to extract structured fields, do not assume all fields belong in the same call. If the model needs to make a decision, make the decision boundary explicit.&lt;/p&gt;

&lt;p&gt;The goal is not minimal tokens. The goal is minimal unnecessary cognition.&lt;/p&gt;

&lt;p&gt;That distinction is important. Some tasks deserve many tokens. A long research synthesis may need a lot of context. A technical incident summary may need careful source retention. A product comparison may need long input and long output. A multi-document comparison may be legitimately expensive.&lt;/p&gt;

&lt;p&gt;The problem is not spending tokens. The problem is spending tokens without knowing what they are buying.&lt;/p&gt;

&lt;h2&gt;
  
  
  This is also a model selection problem
&lt;/h2&gt;

&lt;p&gt;Once you split cognitive tasks, model selection becomes much more interesting. In a one-prompt architecture, you usually choose the strongest model you can afford because the task is messy. The model has to handle everything. It has to read long context, reason, extract, format, validate, and recover from ambiguity.&lt;/p&gt;

&lt;p&gt;But if you split the workflow, you can choose models per cognitive step. A small model can do simple classification. A local model can extract obvious fields. A deterministic parser can normalize dates. A rules engine can validate constraints. A stronger model can handle the genuinely ambiguous reasoning.&lt;/p&gt;

&lt;p&gt;This is where the economics change. Not because you begged the prompt to be shorter, but because you changed the shape of the work. The expensive model becomes a specialist instead of a landfill.&lt;/p&gt;

&lt;p&gt;And yes, I know "landfill" sounds harsh. But many AI systems today are exactly that. They throw all context into one place and hope the biggest model will recycle it into something useful. This works surprisingly often, which is the dangerous part. It works enough to ship a demo. It fails enough to punish you in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  Token economy as observability
&lt;/h2&gt;

&lt;p&gt;A mature AI system should not only log the final response. It should log the token shape of the workflow.&lt;/p&gt;

&lt;p&gt;Which step consumed the most input? Which step produced the most output? Which step retried the most? Which step had the most schema failures? Which step required the strongest model? Which step could be cached? Which step could be replaced by code? Which step actually improved the final decision?&lt;/p&gt;

&lt;p&gt;This is not accounting. This is observability.&lt;/p&gt;

&lt;p&gt;You are not only tracking spend. You are tracking cognitive pressure inside the system. A sudden increase in input tokens may mean your retrieval is bringing too much context. A sudden increase in output tokens may mean the model started explaining instead of structuring. A high retry cost may mean your schema is too complex or your prompt is ambiguous. A high token cost on a low-value decision may mean the workflow needs decomposition. A low token cost with poor quality may mean you compressed away necessary context.&lt;/p&gt;

&lt;p&gt;Again, the metric is not the answer. The metric is the signal. The engineer still needs judgment, which is very inconvenient. We were promised automation and somehow we still need thinking. Rude.&lt;/p&gt;

&lt;h2&gt;
  
  
  The wrong future
&lt;/h2&gt;

&lt;p&gt;The wrong future is easy to imagine. Teams get AI dashboards. Managers see token usage per employee. People are encouraged to "use AI more". Token consumption becomes proof of adoption. Employees learn to generate more prompts because the dashboard rewards activity. Everyone looks productive, costs go up, and quality does not.&lt;/p&gt;

&lt;p&gt;Then leadership announces an AI efficiency initiative. Now everyone must reduce token usage. People use smaller prompts. Quality drops. Nobody knows why. Another dashboard is created. A consultant appears. The circle of life continues.&lt;/p&gt;

&lt;p&gt;This is what happens when the metric becomes the goal. Token usage by itself tells you almost nothing about quality. A great engineer may use fewer tokens because they decomposed the problem properly. Another great engineer may use more tokens because the task genuinely required context. A bad workflow may use few tokens and produce garbage. A good workflow may use many tokens and produce a high-value decision.&lt;/p&gt;

&lt;p&gt;So measuring tokens is not wrong. Judging work by raw token volume is wrong.&lt;/p&gt;

&lt;h2&gt;
  
  
  The better future
&lt;/h2&gt;

&lt;p&gt;The better future is more boring, which is usually a good sign in engineering. Teams treat token metrics as workflow diagnostics. They look at input-output patterns. They identify overloaded prompts. They split tasks where it makes sense. They route context more carefully. They use smaller models for smaller cognitive jobs. They validate structured outputs separately. They measure retries, drift, and failure modes.&lt;/p&gt;

&lt;p&gt;They do not ask "how much AI did you use?" They ask "where did the AI actually add decision value?"&lt;/p&gt;

&lt;p&gt;That is the mindset shift. Tokens are not just a bill. Tokens are a trace of cognitive architecture. They show where a system is bloated. They show where context is duplicated. They show where outputs are too ambitious. They show where models are being used as glue because nobody wanted to design the pipeline. And yes, sometimes they show that the expensive model was actually justified.&lt;/p&gt;

&lt;p&gt;That is fine. The goal is not to make everything cheap. The goal is to make the system honest.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final thought
&lt;/h2&gt;

&lt;p&gt;I think the next stage of AI engineering will not be about who writes the cleverest prompt. It will be about who designs the clearest cognitive pipeline.&lt;/p&gt;

&lt;p&gt;The prompt is not the unit of architecture. The cognitive task is.&lt;/p&gt;

&lt;p&gt;Token balance matters because it gives us a way to inspect that task. Not perfectly. Not automatically. Not as a KPI to punish or reward people. But as a signal that says: maybe this workflow is overloaded, maybe this context is too broad, maybe this output is trying to do too much, maybe this model is stronger than necessary, maybe this task should be split, maybe this step should not use an LLM at all.&lt;/p&gt;

&lt;p&gt;That is where the real token economy lives. Not in spending fewer tokens, but in spending attention where attention is needed.&lt;/p&gt;

&lt;p&gt;If we do that well, the benefits go beyond cost. Lower latency. Smaller models. Cleaner validation. Less format drift. More stable outputs. More inspectable workflows. Systems that are closer to engineering and less close to whispering wishes into a very expensive autocomplete machine.&lt;/p&gt;

&lt;p&gt;Which, to be fair, is still fun.&lt;/p&gt;

&lt;p&gt;But maybe not the future we should build production systems on.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>agents</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Built a native MCP server for Odoo so Claude, Cursor, and Codex can drive it directly</title>
      <dc:creator>Mathias Markl</dc:creator>
      <pubDate>Sun, 26 Apr 2026 22:42:25 +0000</pubDate>
      <link>https://future.forem.com/keshrath/built-a-native-mcp-server-for-odoo-so-claude-cursor-and-codex-can-drive-it-directly-3g1f</link>
      <guid>https://future.forem.com/keshrath/built-a-native-mcp-server-for-odoo-so-claude-cursor-and-codex-can-drive-it-directly-3g1f</guid>
      <description>&lt;p&gt;Most Odoo MCP integrations I came across follow the same pattern: a separate Python process running on your machine (or in a Docker container), talking to Odoo over XML-RPC or JSON-RPC from the outside. It works, but you end up with credentials in env files, a second thing to deploy and keep alive, and no real visibility into what the AI did from inside Odoo.&lt;/p&gt;

&lt;p&gt;I wanted something different, so I built &lt;strong&gt;muk_mcp&lt;/strong&gt; — an open-source addon (LGPL-3) that makes Odoo itself the MCP server. No extra process, no middleware, no RPC bridge. The &lt;code&gt;/mcp&lt;/code&gt; endpoint lives inside the registry and runs through the same ORM as everything else.&lt;/p&gt;

&lt;p&gt;Install it, generate an MCP key from your user preferences, paste the URL into Claude Code, Claude Desktop, Cursor, Windsurf, Codex CLI, or OpenCode. The model gets 15 typed tools covering the full ORM lifecycle — model discovery, schema introspection, search, read, create, update, delete, grouped aggregation, chatter, and method execution.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fev9z4sdfh3lbn7okt74v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fev9z4sdfh3lbn7okt74v.png" alt="muk_mcp architecture" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Per-key scopes, rate limits, and audit log
&lt;/h2&gt;

&lt;p&gt;Each key supports model-level scopes with separate read / write / create / delete permissions. Configurable rate limit per key (default 60 req/min). Every call lands in an audit log with method, tool, model, duration, and status.&lt;/p&gt;

&lt;h2&gt;
  
  
  Custom tools without leaving the UI
&lt;/h2&gt;

&lt;p&gt;Custom tools can be added directly from the backend UI — name, description, JSON Schema for inputs, and Python in a sandboxed &lt;code&gt;safe_eval&lt;/code&gt; context. No code deployment, no server restart. The connected client gets notified when the tool list changes.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fujcn268ocnn6677xvgl1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fujcn268ocnn6677xvgl1.png" alt="Custom tools editor" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Sessions are stateful per the MCP spec and clean themselves up after the configured timeout. Users can revoke their own sessions from preferences.&lt;/p&gt;

&lt;h2&gt;
  
  
  MCP-tagged chatter
&lt;/h2&gt;

&lt;p&gt;Every chatter message authored by an MCP tool is tagged with a small &lt;strong&gt;MCP&lt;/strong&gt; badge next to the author name, so it is immediately obvious which comments, status changes, and tracking entries came from an AI client and which came from a human.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fegsfipgid4rm10dky3sa.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fegsfipgid4rm10dky3sa.png" alt="MCP badge in chatter" width="800" height="429"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub:&lt;/strong&gt; &lt;a href="https://github.com/muk-it/odoo-modules/tree/19.0/muk_mcp" rel="noopener noreferrer"&gt;muk-it/odoo-modules — muk_mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Download:&lt;/strong&gt; &lt;a href="https://apps.odoo.com/apps/modules/19.0/muk_mcp" rel="noopener noreferrer"&gt;apps.odoo.com/apps/modules/19.0/muk_mcp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch:&lt;/strong&gt; &lt;a href="https://youtu.be/zpBTT46tJZ0" rel="noopener noreferrer"&gt;youtu.be/zpBTT46tJZ0&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Would love feedback — what would you want from this kind of integration?&lt;/p&gt;

</description>
      <category>odoo</category>
      <category>mcp</category>
      <category>ai</category>
      <category>opensource</category>
    </item>
    <item>
      <title>The Bug That Cost Me Three Weeks: Why Your SL/TP Logic Is Probably Wrong</title>
      <dc:creator>Vuk Stanic</dc:creator>
      <pubDate>Sun, 26 Apr 2026 22:36:41 +0000</pubDate>
      <link>https://future.forem.com/stanco23/the-bug-that-cost-me-three-weeks-why-your-sltp-logic-is-probably-wrong-30eb</link>
      <guid>https://future.forem.com/stanco23/the-bug-that-cost-me-three-weeks-why-your-sltp-logic-is-probably-wrong-30eb</guid>
      <description>&lt;p&gt;&lt;em&gt;This is the story of a production bug I fixed, turned into a book. It's also why most algorithmic traders fail.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Every algorithmic trader thinks they understand stop-loss and take-profit (SL/TP). Most are wrong. Not subtly wrong — catastrophically wrong in ways that don't show up in backtesting but destroy live systems.&lt;/p&gt;

&lt;p&gt;This is the opening chapter of &lt;a href="https://whop.com/joined/tse-publishing/products/the-circuit-breaker-problem/" rel="noopener noreferrer"&gt;my second book&lt;/a&gt;, and it's the reason I wrote the whole series.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Naive Implementation
&lt;/h2&gt;

&lt;p&gt;Here's what the original code looked like in our production system:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;entry_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sl_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tp_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sl_triggered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tp_triggered&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;check_sl_tp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_price&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.sl_price&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.sl_triggered&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.sl_triggered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;close_position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_price&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.tp_price&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.tp_triggered&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.tp_triggered&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="nf"&gt;close_position&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Looks reasonable, right? Check price against SL/TP levels, set a flag, close if triggered.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;It failed in production. Here's why.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Mode 1: Flag-Based Checking Doesn't Track What Actually Happened
&lt;/h2&gt;

&lt;p&gt;The problem with &lt;code&gt;sl_triggered: bool&lt;/code&gt; is that it tells you &lt;em&gt;that something happened&lt;/em&gt;, but not &lt;em&gt;what actually happened&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Consider this sequence in a fast-moving market:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;T=0:    Price at $105.00, position long with SL at $100.00
T=1:    Price drops to $99.95 (below SL!)
T=2:    Your check runs, sets sl_triggered = true
T=3:    Your system submits close order at $99.95
T=4:    Price bounces back to $100.50
T=5:    Exchange confirms: fill at $99.95
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your code set &lt;code&gt;sl_triggered = true&lt;/code&gt; when price crossed $100.00. The exchange filled you at $99.95. Your flag doesn't tell you what fill price you actually got.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;More critically:&lt;/strong&gt; In step T=4, before the exchange confirmed the fill, your code thought "SL triggered, position closed." But the position wasn't actually closed yet — it was in flight.&lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;state vs event confusion&lt;/strong&gt;. Your flag tracks an event (trigger), not a state (position actually closed).&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure Mode 2: Re-Entry on the Next Tick
&lt;/h2&gt;

&lt;p&gt;Here's where it gets really bad. After the SL triggers:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;T=10:   Price moves back up to $102.00
T=11:   Your strategy sees "price is $102, no open position"
T=12:   Strategy decides to re-enter long
T=13:   New position opened at $102.00
T=14:   Price drops again to $99.90
T=15:   Another SL triggered, another loss
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your system has no memory that the previous close was an SL close. It just sees "no position, price looks good, buy."&lt;/p&gt;

&lt;p&gt;This is the &lt;strong&gt;re-entry problem&lt;/strong&gt; — and it's more expensive than the original loss.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Correct Mental Model
&lt;/h2&gt;

&lt;p&gt;SL/TP should be &lt;strong&gt;state-based&lt;/strong&gt;, not &lt;strong&gt;event-based&lt;/strong&gt;. Instead of "did we trigger?" think "what should we do given the current state and price?"&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;PositionState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;           &lt;span class="c1"&gt;// Position is active, checks are running&lt;/span&gt;
    &lt;span class="n"&gt;ClosePending&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// Close order submitted, waiting for fill&lt;/span&gt;
    &lt;span class="n"&gt;Closed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;         &lt;span class="c1"&gt;// Position fully exited, no more checks&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Position&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;entry_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;sl_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;tp_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;PositionState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="c1"&gt;// Track the close order, not just the trigger&lt;/span&gt;
    &lt;span class="n"&gt;close_order_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;close_trigger_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;opened_at_ns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;close_submitted_at_ns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;closed_at_ns&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Key difference:&lt;/strong&gt; The &lt;code&gt;close_order_id&lt;/code&gt; field tracks &lt;em&gt;the actual close order&lt;/em&gt;, not just a trigger flag. If you have a close order ID, the position is in &lt;code&gt;ClosePending&lt;/code&gt; state. If it's filled, the state transitions to &lt;code&gt;Closed&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;SLTPAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Nothing&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TriggerSL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TriggerTP&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;check_sl_tp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;SLTPAction&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.state&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="nn"&gt;PositionState&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Open&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nn"&gt;SLTPAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Nothing&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_price&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.sl_price&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;SLTPAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TriggerSL&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_price&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.tp_price&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;SLTPAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TriggerTP&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;SLTPAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Nothing&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;on_action&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;Position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;SLTPAction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Decimal&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;SLTPAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Nothing&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(()),&lt;/span&gt;
        &lt;span class="nn"&gt;SLTPAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TriggerSL&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;SLTPAction&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TriggerTP&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;order_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;submit_close_order&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;current_price&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;PositionState&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ClosePending&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.close_order_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;order_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.close_trigger_price&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_price&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;position&lt;/span&gt;&lt;span class="py"&gt;.close_submitted_at_ns&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;current_timestamp_ns&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
            &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why Backtesting Misses This
&lt;/h2&gt;

&lt;p&gt;In backtesting, prices are usually bar-based (OHLC). The SL/TP check happens once per bar at the close. In live trading, you're checking every tick. A tick-based system might check SL/TP 100 times per second.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The bug manifests in live trading because:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Price crosses SL&lt;/li&gt;
&lt;li&gt;Your check runs, returns &lt;code&gt;TriggerSL&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;You submit close order&lt;/li&gt;
&lt;li&gt;Meanwhile, price bounces back above SL&lt;/li&gt;
&lt;li&gt;Your check runs again, sees price above SL, does nothing&lt;/li&gt;
&lt;li&gt;But your close order is still pending...&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The flag-based approach doesn't know that a close order is already in flight. It sees price above SL and would try to trade again.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Actual Production Failure
&lt;/h2&gt;

&lt;p&gt;Our original system had this flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1. Position opened at $100.00, SL = $98.00, TP = $102.00
2. Price drops to $97.50
3. check_sl_tp() sets sl_triggered = true
4. close_position() called
5. Position state set to "closing" (but not "closed")
6. Order submitted to exchange
7. Network latency = 50ms
8. Price bounces back to $99.00
9. check_sl_tp() runs again — price above SL, does nothing
10. Strategy continues to next tick
11. Exchange confirms fill at $97.50
12. Position is now closed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All good so far. But then:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;13. Next tick arrives
14. Strategy sees: "no open position, price is $99, this looks like a buy"
15. New position opened at $99.00
16. Price drops again to $97.50
17. Another SL triggered
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;The problem:&lt;/strong&gt; Steps 13-15 happened while the close order was still in flight. The strategy saw "no position" because the position was in "closing" state but hadn't confirmed "closed" yet.&lt;/p&gt;

&lt;p&gt;We fixed this by adding &lt;strong&gt;close order tracking&lt;/strong&gt; — the system now knows that a close is pending and doesn't allow new positions until the close is confirmed.&lt;/p&gt;

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

&lt;p&gt;Two things:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. State machines over flags.&lt;/strong&gt; Every position should follow a clear state machine: &lt;code&gt;Open → ClosePending → Closed&lt;/code&gt;. Transitions happen on &lt;em&gt;confirmed events&lt;/em&gt;, not on &lt;em&gt;trigger signals&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Backtesting lies to you.&lt;/strong&gt; The bug never appeared in backtesting because we checked once per bar. In live trading, the race condition happens between ticks. Your backtest looks perfect. Your live account doesn't.&lt;/p&gt;




&lt;p&gt;This is Chapter 1 of "The Circuit Breaker Problem" — one of five books in the &lt;a href="https://whop.com/joined/tse-publishing/products/trading-system-engineering-bundle/" rel="noopener noreferrer"&gt;Trading System Engineering Bundle&lt;/a&gt;. All written by an engineer who actually built a production trading engine from scratch. Code templates included.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What's in the bundle:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Order Engine Architecture — FIFO matching, order book data structures&lt;/li&gt;
&lt;li&gt;The Circuit Breaker Problem — SL/TP bugs, trailing stops, re-entry prevention&lt;/li&gt;
&lt;li&gt;Data Pipeline — TVC3 binary format, ring buffers, VPIN&lt;/li&gt;
&lt;li&gt;Risk Management Engineering — commission handling, Kelly criterion, drawdown&lt;/li&gt;
&lt;li&gt;Backtest Architecture — look-ahead bias, slippage modeling, walk-forward analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;$20 per book, $80 for the bundle. Free preview: &lt;a href="https://whop.com/joined/tse-publishing/free-preview-EtI2jE95ZvAIq9/app/" rel="noopener noreferrer"&gt;Book 2, Chapter 1&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>algorithmictrading</category>
      <category>rust</category>
      <category>trading</category>
      <category>engineering</category>
    </item>
    <item>
      <title>on the plane, again</title>
      <dc:creator>Philip Hern</dc:creator>
      <pubDate>Sun, 26 Apr 2026 22:35:23 +0000</pubDate>
      <link>https://future.forem.com/shrouwoods/on-the-plane-again-3j90</link>
      <guid>https://future.forem.com/shrouwoods/on-the-plane-again-3j90</guid>
      <description>&lt;h2&gt;
  
  
  thesis
&lt;/h2&gt;

&lt;p&gt;my opinion on using wifi on a plane has shifted. i do not think it is the right default for everyone in every situation, but when i am traveling alone, especially for work, i have started to treat a connected cabin as a feature. it takes hours that used to feel like pure waiting, time i was just trying to burn through, and turns them into a stretch where i can work with a surprisingly solid level of focus and relatively few distractions.&lt;/p&gt;

&lt;h2&gt;
  
  
  context
&lt;/h2&gt;

&lt;p&gt;a few weeks ago i wrote about how torn i still was on this topic in &lt;a href="https://philliant.com/posts/20260324-wifi-on-planes/" rel="noopener noreferrer"&gt;plane wifi: when the cabin forced disconnect&lt;/a&gt;. that piece was an honest inventory of the tradeoffs. this one is an update from the other side of the choice, after i have spent more flights actually buying the pass and sitting down to work instead of debating it.&lt;/p&gt;

&lt;h2&gt;
  
  
  argument
&lt;/h2&gt;

&lt;p&gt;when i am alone and the trip is for my job, the row stops feeling like a cage and starts feeling like a quiet room with bad legroom. i already have headphones in, so the cabin noise is under control. notifications are fewer than at my desk, nobody is making noise by my office door, and the margin of "things i could be doing instead" feels narrower. it is not peace and it is not deep rest, but it is a usable kind of concentration.&lt;/p&gt;

&lt;p&gt;i am now using that block to write, to debug, to plan, and to close loops i would otherwise push to after i land. i go in knowing i will get a meaningful amount done, and that expectation makes the clock feel less stuck. the time still passes at the same speed, but it passes with output attached, and that changes how it feels in my body.&lt;/p&gt;

&lt;p&gt;i also have a concrete proof point that this mode is not just talk. i completely stood up my personal website while airborne, end to end, in one of those sessions. right now i am on a plane again, getting ahead for an in-person meeting so i can walk in prepared instead of scrambling on the jet bridge.&lt;/p&gt;

&lt;h3&gt;
  
  
  tension or counterpoint
&lt;/h3&gt;

&lt;p&gt;i am not arguing that every person should pay for wifi on every flight. shared trips, family logistics, the middle seat, motion sickness, or the simple need to be offline are all good reasons to skip it. economy is still a bad default office for anyone who needs space or quiet that the cabin cannot give. my point is narrower. for me, in the situations where it fits, the cost of the pass is cheaper than the opportunity cost of treating the whole flight as lost time.&lt;/p&gt;

&lt;h2&gt;
  
  
  closing
&lt;/h2&gt;

&lt;p&gt;i will probably still sometimes want the cabin to be an excuse to be unreachable. when i do, i can leave the wifi off. when i do not, i am glad the option exists, and i am using it on purpose.&lt;/p&gt;

&lt;h2&gt;
  
  
  further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://en.wikipedia.org/wiki/In-flight_connectivity" rel="noopener noreferrer"&gt;in-flight connectivity&lt;/a&gt;, background on how internet reaches aircraft&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  related on this site
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260324-wifi-on-planes/" rel="noopener noreferrer"&gt;plane wifi: when the cabin forced disconnect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/series/commentary/" rel="noopener noreferrer"&gt;commentary series&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>commentary</category>
      <category>travel</category>
      <category>wifi</category>
      <category>work</category>
    </item>
    <item>
      <title>logic</title>
      <dc:creator>Philip Hern</dc:creator>
      <pubDate>Sun, 26 Apr 2026 22:35:21 +0000</pubDate>
      <link>https://future.forem.com/shrouwoods/logic-3360</link>
      <guid>https://future.forem.com/shrouwoods/logic-3360</guid>
      <description>&lt;h2&gt;
  
  
  thesis
&lt;/h2&gt;

&lt;p&gt;learning basic logic is one of the most useful, durable skills i can recommend to anyone, regardless of profession. the english-language version of if/then/else is a thinking tool that works everywhere, never expires, and quietly compounds into better decisions over a lifetime.&lt;/p&gt;

&lt;h2&gt;
  
  
  context
&lt;/h2&gt;

&lt;p&gt;most people associate logic with code, math, or a philosophy classroom. that framing is too narrow. logic is just structured cause-and-effect thinking, and the simplest version of it sounds exactly like english:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;if this, then that, else that other thing&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;once you can hold that pattern in your head on purpose, it changes how you plan, diagnose, design, and interpret almost everything in front of you. you do not need a programming language to use it. you just need to be willing to slow down for a beat and think in branches instead of straight lines.&lt;/p&gt;

&lt;h3&gt;
  
  
  a few familiar gates
&lt;/h3&gt;

&lt;p&gt;here a few small pictures to demonstrate simple examples of logic gates that you already use in daily life. this just illustrates it so you can literally follow along with the logic gates as each situation progresses.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;and&lt;/strong&gt; both must be true for the outcome to be true&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;or&lt;/strong&gt; at least one true is enough&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;not&lt;/strong&gt; you follow the opposite branch of the test&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;the flow is the same kind of small chart you would sketch on a napkin.&lt;/p&gt;

&lt;p&gt;{{&amp;lt; mermaid &amp;gt;}}&lt;br&gt;
flowchart TB&lt;br&gt;
subgraph g_and [and, both need to be true]&lt;br&gt;
direction LR&lt;br&gt;
w[good weather?] --&amp;gt; and1{and}&lt;br&gt;
f[afternoon free?] --&amp;gt; and1&lt;br&gt;
and1 --&amp;gt;|yes| hike[go hiking]&lt;br&gt;
and1 --&amp;gt;|no| home[stay in]&lt;br&gt;
end&lt;br&gt;
subgraph g_or [or, at least one is enough]&lt;br&gt;
direction LR&lt;br&gt;
car[friend can drive?] --&amp;gt; or1{or}&lt;br&gt;
bus[transit is running?] --&amp;gt; or1&lt;br&gt;
or1 --&amp;gt;|yes| go[you can get there]&lt;br&gt;
or1 --&amp;gt;|no| stuck[you are stuck]&lt;br&gt;
end&lt;br&gt;
subgraph g_not [not, the opposite branch of the test]&lt;br&gt;
direction LR&lt;br&gt;
pow[power on?] --&amp;gt;|no| br[check the breaker]&lt;br&gt;
pow --&amp;gt;|yes| next1[next check in the chain]&lt;br&gt;
end&lt;br&gt;
{{&amp;lt; /mermaid &amp;gt;}}&lt;/p&gt;

&lt;p&gt;none of that requires a keyboard. it is the same branch habit as the if/then/else line in the last section, just drawn with a few boxes.&lt;/p&gt;

&lt;h2&gt;
  
  
  argument
&lt;/h2&gt;

&lt;h3&gt;
  
  
  logic is a thinking tool, not a coding tool
&lt;/h3&gt;

&lt;p&gt;the if/then/else pattern is older than any programming language. when i write a small script, i am formalizing the same branching i already do when i pick what to wear, route around traffic, or decide how to respond when something at work breaks. the keyboard is incidental, the structure is the point.&lt;/p&gt;

&lt;p&gt;this kind of structured thinking is what moves me from "i feel stuck" to "what is the next decision, and what are the branches under it". that small shift, from a vague feeling to a concrete branch point, is where most of the leverage comes from.&lt;/p&gt;

&lt;h3&gt;
  
  
  where it pays off in normal life
&lt;/h3&gt;

&lt;p&gt;once you start noticing branches, you see them everywhere. planning a day with kids becomes a small logic tree. if the weather holds, we hike. if it does not, we move to the indoor option. if both fail, we cancel and reschedule. naming the branches up front means the day does not collapse when conditions change.&lt;/p&gt;

&lt;p&gt;troubleshooting has the same shape. when something is not working, i walk a tree out loud. if the appliance has power, then check the next link. if not, then check the breaker. each step rules out a branch and shrinks the search space.&lt;/p&gt;

&lt;p&gt;designing a process at work has the same bones. i list the conditions first, then the path each one takes. naming the branches early makes the design easier to explain and easier to fix later.&lt;/p&gt;

&lt;p&gt;understanding behavior is harder, but the structure still helps. people are not perfectly logical, but their patterns often are. if my kid is tired, then certain tantrums become more likely. if a colleague is overloaded, then certain reactions track. recognizing the antecedent makes the response less personal and easier to handle.&lt;/p&gt;

&lt;p&gt;reverse engineering is the same thing run backward. when i look at a result and want to understand how it got there, i walk the logic in reverse. if this output exists, then these inputs and conditions must have been true. if not, then the model i had in my head is wrong, and that gap is useful information on its own.&lt;/p&gt;

&lt;h3&gt;
  
  
  the tool never goes out of style
&lt;/h3&gt;

&lt;p&gt;frameworks change. tools change. programming languages come and go. if/then/else does not. it is a structure of thought, not a piece of technology, which is why it keeps working in domains it was never designed for. cooking, parenting, negotiations, medical decision trees, customer support scripts, and legal arguments all lean on the same scaffolding.&lt;/p&gt;

&lt;p&gt;i find real comfort in skills that age well. so much of what i learn in tech has a short half-life now. logic does not. once i have it, i have it for good.&lt;/p&gt;

&lt;h3&gt;
  
  
  applying it broadly is what makes it powerful
&lt;/h3&gt;

&lt;p&gt;a tool that works in one place is useful. a tool that works everywhere is leverage. logic works everywhere because every domain has cause and effect, conditions, and outcomes. that generality is the multiplier, and it is the same kind of cross-domain value i wrote about with &lt;a href="https://philliant.com/posts/20260327-adaptability/" rel="noopener noreferrer"&gt;adaptability&lt;/a&gt;. the principle stays steady while the surface details swap.&lt;/p&gt;

&lt;h3&gt;
  
  
  learn it as early as you can
&lt;/h3&gt;

&lt;p&gt;the earlier this gets internalized, the more downstream decisions inherit it. a kid who can think in branches asks better questions, accepts fewer "because i said so" answers, and gradually builds a habit of checking conditions before reacting. that habit then runs in the background for the rest of their life.&lt;/p&gt;

&lt;p&gt;i think about this with my own kids, and i think about it for myself. every year i wait to make decisions more deliberately is a year of slightly noisier decisions stacked behind me.&lt;/p&gt;

&lt;h3&gt;
  
  
  the butterfly effect of better decisions
&lt;/h3&gt;

&lt;p&gt;small improvements in single decisions do not look like much in isolation. two paths that differ by one degree at the start can end up far apart over a long enough timeline. better daily decisions, even by a thin margin, compound the same way &lt;a href="https://philliant.com/posts/20260406-little-by-little-a-little-becomes-a-lot/" rel="noopener noreferrer"&gt;a little becomes a lot&lt;/a&gt; does for habits. the quality of the inputs, repeated across years, becomes the quality of the life.&lt;/p&gt;

&lt;p&gt;logic is one of the cheapest ways i know to nudge that compounding in a good direction.&lt;/p&gt;

&lt;h3&gt;
  
  
  tension or counterpoint
&lt;/h3&gt;

&lt;p&gt;logic on its own is not the whole answer. real situations carry emotion, ambiguity, missing information, and people who do not behave according to clean rules. if i treat every interaction like a flowchart, i lose intuition, empathy, and the ability to sit with uncertainty.&lt;/p&gt;

&lt;p&gt;the skill is to use logic as scaffolding, not as a replacement for judgment. i map the branches i can see, then i listen for the part that the branches do not capture. both layers matter, and the logical part actually helps the intuitive part by giving it a clean place to stand.&lt;/p&gt;

&lt;h2&gt;
  
  
  closing
&lt;/h2&gt;

&lt;p&gt;this is one of the cheapest, most durable investments anyone can make. learn the english-language form of if/then/else. practice naming the conditions and the branches in your own life. apply it to planning, troubleshooting, designing, and understanding the people around you.&lt;/p&gt;

&lt;p&gt;learn it once and you keep it forever. apply it everywhere and it compounds. not many skills pay back like that.&lt;/p&gt;

&lt;h2&gt;
  
  
  further reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Propositional_calculus" rel="noopener noreferrer"&gt;propositional logic&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Decision_tree" rel="noopener noreferrer"&gt;decision tree&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Critical_thinking" rel="noopener noreferrer"&gt;critical thinking&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  related on this site
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260327-adaptability/" rel="noopener noreferrer"&gt;adaptability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260406-little-by-little-a-little-becomes-a-lot/" rel="noopener noreferrer"&gt;little by little, a little becomes a lot&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/posts/20260410-comfortable-being-uncomfortable/" rel="noopener noreferrer"&gt;comfortable being uncomfortable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://philliant.com/series/commentary/" rel="noopener noreferrer"&gt;commentary series&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>logic</category>
      <category>thinking</category>
      <category>decisionmaking</category>
      <category>problemsolving</category>
    </item>
    <item>
      <title>My Wallet Experiences</title>
      <dc:creator>Hope</dc:creator>
      <pubDate>Sun, 26 Apr 2026 22:29:11 +0000</pubDate>
      <link>https://future.forem.com/hopebestworld/my-wallet-experiences-48fn</link>
      <guid>https://future.forem.com/hopebestworld/my-wallet-experiences-48fn</guid>
      <description>&lt;p&gt;What I did: This week, I went from generating raw key pairs in the terminal to building a web app that connects to browser wallets like Phantom.&lt;/p&gt;

&lt;p&gt;What surprised me: The biggest "aha" moment was realizing that a wallet is a UI for a cryptographic key pair that serves as my universal identity.&lt;/p&gt;

&lt;p&gt;What's next: I'm excited to move past the basics and start writing my own on-chain programs! &lt;/p&gt;

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

</description>
      <category>100daysofsolana</category>
    </item>
    <item>
      <title>I Saved 2 Hours a Week by Automating My Changelog — Here's Exactly How</title>
      <dc:creator>shiplog-bot</dc:creator>
      <pubDate>Sun, 26 Apr 2026 22:21:02 +0000</pubDate>
      <link>https://future.forem.com/shiplogbot/i-saved-2-hours-a-week-by-automating-my-changelog-heres-exactly-how-5gjg</link>
      <guid>https://future.forem.com/shiplogbot/i-saved-2-hours-a-week-by-automating-my-changelog-heres-exactly-how-5gjg</guid>
      <description>&lt;p&gt;Last Tuesday, I spent 45 minutes staring at a spreadsheet of commit messages trying to turn "fix: prevent undefined refs in memo" into something a human would actually want to read.&lt;/p&gt;

&lt;p&gt;That was the moment I realized I'd been doing changelog work wrong for three years.&lt;/p&gt;

&lt;p&gt;I'm not talking about minor annoyance—I'm talking about the slow bleed of context-switching that kills momentum. Every Friday, I'd context-switch from shipping features into "changelog mode," where I'd hunt through 30-50 commits, decode abbreviations, figure out what actually mattered to users vs. what was internal scaffolding, and then rewrite everything in three different voices so different stakeholders could understand it.&lt;/p&gt;

&lt;p&gt;Two hours. Every. Single. Week.&lt;/p&gt;

&lt;p&gt;That's roughly 100 hours a year I wasn't building, debugging, or thinking clearly.&lt;/p&gt;

&lt;p&gt;So I built something to fix it: &lt;strong&gt;Proseflow&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It's an AI changelog generator that pulls your GitHub commits, understands context through OpenAI's GPT-4o-mini, and spits out a polished changelog in three tones: developer-focused, user-friendly, and executive summary. I'm open-sourcing my process here because if you ship software and maintain a changelog, this probably applies to you too.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Problem (Before)
&lt;/h2&gt;

&lt;p&gt;Here's what my typical Friday looked like:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open GitHub, filter commits from the past week&lt;/li&gt;
&lt;li&gt;Copy-paste into a markdown file&lt;/li&gt;
&lt;li&gt;Rewrite 60% of them because "chore: update deps" doesn't tell users anything&lt;/li&gt;
&lt;li&gt;Split into three separate sections (for devs, for users, for leadership)&lt;/li&gt;
&lt;li&gt;Proofread, add links, cross-check with PR titles&lt;/li&gt;
&lt;li&gt;Repeat next week&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The context-switch tax was brutal. By the time I finished, I'd lost the thread on the feature I was actually building.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Solution (After)
&lt;/h2&gt;

&lt;p&gt;I built Proseflow to do this in &lt;strong&gt;one click&lt;/strong&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Real Example: Before → After
&lt;/h3&gt;

&lt;p&gt;Here's what actually happened last week with a real commit batch:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Raw commits:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fix: resolve race condition in useAuth hook
feat: add webhook retry logic with exponential backoff
chore: bump @openai/sdk to 4.52.0
fix: sanitize user input in form validator
perf: memoize expensive cart calculations
docs: update API reference for batch endpoints
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Proseflow output (3 versions):&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Developer Tone:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## v1.2.4 - Jan 15, 2025&lt;/span&gt;

&lt;span class="gu"&gt;### Features&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Webhook Retry System**&lt;/span&gt;: Implemented exponential backoff for failed 
  webhook deliveries. Reduces manual retry overhead and improves reliability 
  for payment integrations.

&lt;span class="gu"&gt;### Fixes&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Fixed race condition in &lt;span class="sb"&gt;`useAuth`&lt;/span&gt; hook that could cause stale token 
  references in concurrent renders
&lt;span class="p"&gt;-&lt;/span&gt; Sanitized form validator inputs to prevent XSS vulnerabilities

&lt;span class="gu"&gt;### Performance&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Memoized cart total calculations (expensive deep-object comparisons now 
  cached per session)

&lt;span class="gu"&gt;### Dependencies&lt;/span&gt;
&lt;span class="p"&gt;-&lt;/span&gt; Updated &lt;span class="sb"&gt;`@openai/sdk`&lt;/span&gt; to 4.52.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;User-Friendly Tone:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## What's New in v1.2.4&lt;/span&gt;

We've made your experience more reliable and faster:
&lt;span class="p"&gt;
-&lt;/span&gt; &lt;span class="gs"&gt;**Payments are now more reliable**&lt;/span&gt;: If a payment notification fails, 
  we automatically retry it. No more missing transactions.
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Faster checkout**&lt;/span&gt;: Your cart now loads noticeably faster, 
  especially with lots of items
&lt;span class="p"&gt;-&lt;/span&gt; &lt;span class="gs"&gt;**Improved security**&lt;/span&gt;: We fixed some edge cases in our form handling 
  that could have exposed user data (already patched in production)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Executive Tone:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gu"&gt;## v1.2.4 Release Summary&lt;/span&gt;

&lt;span class="gs"&gt;**Impact**&lt;/span&gt;: Improved system reliability and user experience
&lt;span class="p"&gt;-&lt;/span&gt; Webhook retry logic reduces failed payment notifications by ~15%
&lt;span class="p"&gt;-&lt;/span&gt; 40% faster cart rendering on high-volume carts
&lt;span class="p"&gt;-&lt;/span&gt; Security hardening in form validation (no user impact; proactive fix)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Time to generate all three:&lt;/strong&gt; 8 seconds.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Time I previously spent:&lt;/strong&gt; 45 minutes.&lt;/p&gt;




&lt;h2&gt;
  
  
  How It Actually Works
&lt;/h2&gt;

&lt;p&gt;Proseflow is built on three core pieces:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. GitHub OAuth (via NextAuth)
&lt;/h3&gt;

&lt;p&gt;When you connect Proseflow, it asks for read-only access to your repositories. It never commits, pushes, or modifies anything—just reads commit history.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Commit Intelligence (GPT-4o-mini)
&lt;/h3&gt;

&lt;p&gt;I'm using OpenAI's GPT-4o-mini (not the full GPT-4o—cost matters) to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Parse commit messages and extract intent&lt;/li&gt;
&lt;li&gt;Identify breaking changes automatically&lt;/li&gt;
&lt;li&gt;Group related commits into features/fixes/chores&lt;/li&gt;
&lt;li&gt;Generate three independent versions&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The prompt engineering was the hard part. The model needs context about what actually matters—a security fix matters differently than a dependency bump.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. One-Click Export
&lt;/h3&gt;

&lt;p&gt;The changelog renders as markdown you can copy, edit, and publish. No vendor lock-in.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Technical Stack
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Frontend&lt;/strong&gt;: Next.js 14 (App Router)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auth&lt;/strong&gt;: NextAuth.js (GitHub OAuth)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI&lt;/strong&gt;: OpenAI GPT-4o-mini&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deployment&lt;/strong&gt;: Vercel&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Styling&lt;/strong&gt;: Tailwind CSS&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Real Time Savings
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Before&lt;/th&gt;
&lt;th&gt;After&lt;/th&gt;
&lt;th&gt;Weekly Savings&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Commit parsing&lt;/td&gt;
&lt;td&gt;15 min&lt;/td&gt;
&lt;td&gt;0 min&lt;/td&gt;
&lt;td&gt;15 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tone rewriting (3x)&lt;/td&gt;
&lt;td&gt;20 min&lt;/td&gt;
&lt;td&gt;0 min&lt;/td&gt;
&lt;td&gt;20 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Proofing/editing&lt;/td&gt;
&lt;td&gt;10 min&lt;/td&gt;
&lt;td&gt;3 min&lt;/td&gt;
&lt;td&gt;7 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Total&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;45 min&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;3 min&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;42 min/week&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;42 minutes × 52 weeks = &lt;strong&gt;36 hours per year&lt;/strong&gt; back in your pocket.&lt;/p&gt;




&lt;h2&gt;
  
  
  Being Honest About What This Is
&lt;/h2&gt;

&lt;p&gt;Proseflow is currently &lt;strong&gt;free during beta&lt;/strong&gt;. That won't last forever, but right now I'm optimizing for real feedback over revenue.&lt;/p&gt;

&lt;p&gt;Is it perfect? No. Sometimes it groups commits weirdly or makes tone choices you'd do differently. But it handles 80% of the grunt work automatically—and that's the meaningful part.&lt;/p&gt;

&lt;p&gt;(Full transparency: the core system was built with significant AI assistance, as part of an autonomous agent experiment. The code is real, the GitHub integration is real, and the generated changelogs are real.)&lt;/p&gt;




&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;If you ship software on a regular cadence, spend 2 minutes trying it:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;→ &lt;a href="https://proseflow-v1.vercel.app" rel="noopener noreferrer"&gt;proseflow-v1.vercel.app&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Sign in with GitHub, pick a repo, choose a date range, and see what it generates. No credit card. No commitment.&lt;/p&gt;

&lt;p&gt;I'd love feedback—especially if there's a tone that doesn't land or a commit type it consistently misses. Drop a comment below or open an issue on the repo.&lt;/p&gt;

&lt;p&gt;Even if it saves you half the time it saves me, that's worth the two-minute setup.&lt;/p&gt;

</description>
      <category>productivity</category>
      <category>github</category>
      <category>devtools</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Authenticating AI Agents Without Shared Secrets</title>
      <dc:creator>Pico</dc:creator>
      <pubDate>Sun, 26 Apr 2026 22:20:54 +0000</pubDate>
      <link>https://future.forem.com/piiiico/authenticating-ai-agents-without-shared-secrets-3h1f</link>
      <guid>https://future.forem.com/piiiico/authenticating-ai-agents-without-shared-secrets-3h1f</guid>
      <description>&lt;h2&gt;
  
  
  The credentials problem nobody's fixing
&lt;/h2&gt;

&lt;p&gt;Every AI agent framework in 2026 has the same security architecture: environment variables. Your agent gets an API key. That key lives in memory for the entire session. If the agent is compromised, the key is compromised. If the platform hosting your env vars is breached (Vercel disclosed unauthorized access to internal systems in March 2026), every agent using those credentials is exposed.&lt;/p&gt;

&lt;p&gt;This isn't hypothetical. An autonomous agent with database access deleted production volumes on Railway earlier this year. The post-mortem blamed the model. The actual failure: the agent had root-level database credentials with no expiry, no scope limitation, and no way to trace which session performed the deletion.&lt;/p&gt;

&lt;p&gt;Traditional auth assumes a human is present. OAuth requires browser redirects. API keys are shared secrets that live forever until someone remembers to rotate them. Neither works when the caller is software running unattended across organizational boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  What agents actually need
&lt;/h2&gt;

&lt;p&gt;Three properties, none of which existing auth provides.&lt;/p&gt;

&lt;p&gt;The agent must prove who it is without revealing a reusable credential. If you intercept the proof, you can't replay it tomorrow. The token must expire: issued for this session, this task, this hour. And any service receiving a request must verify the identity offline, without calling back to a central registry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ed25519 JWT: the boring solution
&lt;/h2&gt;

&lt;p&gt;We issue each agent session a signed JWT using Ed25519 (the same EdDSA curve used in Visa tap-to-pay). The token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://agentlair.dev"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acc_7kQ2mN"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://service.example"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1714176000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1714172400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jti"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"aat_xR9f2k"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"al_scopes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"email:send"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"api:read"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"al_audit_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://agentlair.dev/v1/audit/aat_xR9f2k"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One hour TTL. Scoped to the specific service the agent calls. Every token gets a unique ID (&lt;code&gt;jti&lt;/code&gt;) linked to a full audit trail.&lt;/p&gt;

&lt;p&gt;Verification is standard JWKS (RFC 7517). The public key sits at &lt;code&gt;/.well-known/jwks.json&lt;/code&gt;. Any HTTP client can fetch it, cache it for an hour, and verify tokens offline from that point. A Go service, a Python function, a Cloudflare Worker all verify the same token the same way. No SDK. No vendor lock-in.&lt;/p&gt;

&lt;p&gt;Why Ed25519? 64-byte signatures (half of RSA-2048), deterministic signing (no nonce reuse vulnerability), and the clearest post-quantum migration path. The signing key never leaves the platform. Agents receive tokens; they don't hold signing material.&lt;/p&gt;

&lt;h2&gt;
  
  
  How agents pay for things
&lt;/h2&gt;

&lt;p&gt;Identity alone doesn't solve the economics. When an autonomous agent needs to send an email or call a paid API, who pays? The traditional answer: the developer's credit card, via an API key. Which loops back to shared secrets.&lt;/p&gt;

&lt;p&gt;We use HTTP 402 (Payment Required) with the x402 protocol. When an agent exceeds its free tier, the service returns a 402 with payment terms instead of a 429:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"x402Version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"accepts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"scheme"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"exact"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"network"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"eip155:8453"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"maxAmountRequired"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"10000"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"asset"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"payTo"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0x90EE..."&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent signs an EIP-3009 &lt;code&gt;transferWithAuthorization&lt;/code&gt;. A USDC transfer on Base, authorized off-chain. 0.01 USDC per email. No gas estimation, no wallet popup, no human in the loop. Payment settles after the operation succeeds. The agent's economic activity is as auditable as its identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  What production feedback says
&lt;/h2&gt;

&lt;p&gt;The crewAI RFC on agent identity (#5561) has drawn 12 comments from 7 contributors in six days. BobRenze, who run production multi-agent systems, confirmed they hit the exact OWASP ASI03/ASI07 gap this architecture addresses. Their systems independently converged on the same hybrid: session tokens for identity, per-call tokens for actions.&lt;/p&gt;

&lt;p&gt;Microsoft's Agent Governance Toolkit (open-sourced April 2, 1,266 GitHub stars) arrived at Ed25519 DIDs with dynamic trust scoring without coordination. Different scope (single-deployment, not cross-org), but when two independent projects converge on the same cryptographic primitive and trust model, the design space is probably real.&lt;/p&gt;

&lt;p&gt;Meanwhile, only 18% of security leaders say their identity systems handle agents (Strata survey, April 2026). The gap between "agents are in production" and "agents have identity" is growing.&lt;/p&gt;

&lt;h2&gt;
  
  
  What no framework ships
&lt;/h2&gt;

&lt;p&gt;Every agent framework handles tool use, memory, and orchestration. None handle identity. Your agent can query a database, write code, send emails, manage files. It can't prove who it is to the service on the other end of the call.&lt;/p&gt;

&lt;p&gt;That's the layer we built. Ed25519 JWTs, JWKS verification, x402 payments, behavioral trust scoring across organizational boundaries. Standard protocols. One-hour token lifetimes.&lt;/p&gt;

&lt;p&gt;The spec and implementation: &lt;a href="https://agentlair.dev" rel="noopener noreferrer"&gt;agentlair.dev&lt;/a&gt;. The crewAI integration discussion: &lt;a href="https://github.com/crewAIInc/crewAI/issues/5561" rel="noopener noreferrer"&gt;crewAI #5561&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>security</category>
      <category>authentication</category>
      <category>agents</category>
    </item>
    <item>
      <title>How Identity Actually Works on Solana</title>
      <dc:creator>Hope</dc:creator>
      <pubDate>Sun, 26 Apr 2026 22:14:51 +0000</pubDate>
      <link>https://future.forem.com/hopebestworld/how-identity-actually-works-on-solana-2gbh</link>
      <guid>https://future.forem.com/hopebestworld/how-identity-actually-works-on-solana-2gbh</guid>
      <description>&lt;h2&gt;
  
  
  Beyond Passwords: Understanding Identity on Solana
&lt;/h2&gt;

&lt;p&gt;In the Web2 world, your identity is basically a row in a database owned by someone else. You have a username for GitHub and an email for Google. You rely on these companies to hash your password, handle forgotten password emails, and keep your data safe. On Solana, there are no databases or admins. Your identity is built on the keypair.&lt;/p&gt;

&lt;h3&gt;
  
  
  Think of it Like an SSH Key
&lt;/h3&gt;

&lt;p&gt;If you’ve ever connected to a server using an SSH key, you already understand Solana. You generate a public key and a private key. You share the public one and keep the private one hidden. To prove who you are, you use the private key to sign a request.&lt;/p&gt;

&lt;p&gt;On Solana, the entire network is the server. Your public key is your address, and your private key is your proof of ownership. To move money or talk to a smart contract, you just sign the request with that private key.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Addresses Look So Weird
&lt;/h3&gt;

&lt;p&gt;Instead of a username, a Solana address looks like a long string of random characters. This string is encoded in &lt;strong&gt;Base58&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Solana uses Base58 to prevent human errors. It removes confusing characters that look alike, such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The number zero (&lt;strong&gt;0&lt;/strong&gt;)&lt;/li&gt;
&lt;li&gt;The capital letter &lt;strong&gt;O&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The capital letter &lt;strong&gt;I&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;The lowercase letter &lt;strong&gt;l&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This makes it much harder to make a mistake when you are copying or reading an address.&lt;/p&gt;

&lt;h3&gt;
  
  
  You Are the Only Boss
&lt;/h3&gt;

&lt;p&gt;In Web2, a company can lock your account or get hacked. They own your data. You just have permission to use it. On Solana, ownership is &lt;strong&gt;cryptographic&lt;/strong&gt;. Only the person holding the private key can make changes.&lt;/p&gt;

&lt;p&gt;There is no password reset button. If you lose your private key, you lose your account forever. While that sounds scary, it also means no company or admin can ever block you or take your funds. You are in total control.&lt;/p&gt;

&lt;h3&gt;
  
  
  One Key for Everything
&lt;/h3&gt;

&lt;p&gt;Because your identity is based on math, it works everywhere. You just connect your wallet, and every game, marketplace, or exchange on the network instantly knows it's you. It’s like having a universal passport that works across the entire internet without needing anyone's permission.&lt;/p&gt;

</description>
      <category>100daysofsolana</category>
      <category>solana</category>
      <category>web3</category>
      <category>blockchain</category>
    </item>
    <item>
      <title>How to Grow Your Network</title>
      <dc:creator>Yaroslav Tkachenko</dc:creator>
      <pubDate>Sun, 26 Apr 2026 22:14:44 +0000</pubDate>
      <link>https://future.forem.com/sap1ens/how-to-grow-your-network-21od</link>
      <guid>https://future.forem.com/sap1ens/how-to-grow-your-network-21od</guid>
      <description>&lt;p&gt;Growing your network is likely one of the most important things you can do before becoming a full-time solopreneur. And yet, many engineers don’t know how to approach it.&lt;/p&gt;

&lt;p&gt;Having a large network is important for many reasons. In a services/consulting business, your network (and the references you get from it) becomes the source of your contracts. In a product business, it generates users. &lt;/p&gt;

&lt;p&gt;And even if you don’t become a solopreneur, your network can provide better employment opportunities, so growing it is a no-brainer. &lt;/p&gt;

&lt;h2&gt;
  
  
  What is Network?
&lt;/h2&gt;

&lt;p&gt;But what is it, really? Your network is not the number of followers you have. It’s about people you &lt;em&gt;can reach out to&lt;/em&gt;. People who will respond when they hear from you. And, most importantly, people who will help, refer, recommend, try, and offer their time. &lt;/p&gt;

&lt;p&gt;So, how do you get people like these?&lt;/p&gt;

&lt;h2&gt;
  
  
  The Most Important Rule
&lt;/h2&gt;

&lt;p&gt;The rule is simple: you need to be able to &lt;em&gt;provide value&lt;/em&gt;. It may sound transactional, but it works. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Agree to provide feedback on a product MVP. Give advice on how to fix a problem. Write a recommendation later. Support someone’s promotion case. Meet with an intern for a coffee.&lt;/strong&gt; And so on. &lt;/p&gt;

&lt;p&gt;You’ll likely not benefit from it. But you’re building &lt;em&gt;relationships&lt;/em&gt; , not clients. This is a long game to play.&lt;/p&gt;

&lt;p&gt;And always make sure to follow up when you promise something. You want to be remembered as a person who delivers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ways to Grow
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Your Current Job
&lt;/h3&gt;

&lt;p&gt;This is the most effective way. People really value coworkers they previously enjoyed working with. Of course, you have to be that coworker. Offer help. Offer feedback. Unblock. Support. Be generous with your time. &lt;/p&gt;

&lt;p&gt;When I worked at Shopify as a Staff Engineer, I had more 1:1s than my manager. Shopify is a really big organization, and I wanted to engage and collaborate with many brilliant engineers. I had three types of 1:1 meetings:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;My teammates. In some companies with heavy processes, you may end up talking to your teammates all the time. Especially if you work in the same location. In other places, especially remote, it’s not always the case. So, make sure you spend enough face-to-face time and genuinely try to be helpful. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;My mentees. Shopify had an official mentorship program, but I also unofficially started mentoring more junior engineers. Just because I recognized the potential and wanted to help. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;My peers. Staff and Senior Staff engineers who were responsible for other parts of the Data Platform. Some were in different departments. I wanted to stay up-to-date on major initiatives (ideally try to influence them), and offer my help in aligning and unblocking major projects.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some of these relationships really paid out: when I joined the next company as a Founding Engineer, I built my team exclusively from the folks I previously worked with and mentored. &lt;/p&gt;

&lt;p&gt;Also, this might be another reason to switch jobs. I’m not proposing job hopping, but the math is hard to beat: working in 3 places 3-4 years each will generally grow your network much faster than staying in one place for 10 years.&lt;/p&gt;

&lt;h3&gt;
  
  
  Events
&lt;/h3&gt;

&lt;p&gt;Conferences and meetups are really powerful ways to meet people you’ve never interacted with before. I’m an introvert, but I still find these events really useful. In retrospect, they made a huge impact on my career. &lt;/p&gt;

&lt;p&gt;And again, many engineers don’t really know how to properly attend these events if they want to grow their network. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;First of all, you’re not there to watch talks&lt;/strong&gt;. Most conferences nowadays record their talks; they’ll be available for watching later. There are only a few reasons to spend your time on someone's talk: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;The content is extremely relevant for you right now, and you can’t wait a few weeks for the recordings to be available.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You want to support the presenter and engage them after the talk.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You’re planning to write a post about the conference :)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So what do you do instead? You go to the “hallway track”: a place where most of the people hang out outside of the talks. Generally, something like an expo hall. &lt;/p&gt;

&lt;p&gt;Start talking to people. Introduce yourself, but don’t talk about you all the time! You are there to listen. Ask about people’s challenges. And when you get enough information, offer to help. Offer to try their product. Offer to provide feedback. Offer to go over their problem yourself, or introduce them to someone else who can help. &lt;strong&gt;See The Most Important Rule again - be helpful!&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you already offer a service (maybe part-time), you can come much more prepared. &lt;/p&gt;

&lt;p&gt;Many fancy conferences these days show attendees in their apps. So, do your homework and analyze them. Find people from relevant companies, write them down. You can even reach out before the event and say something like “Hey X! I noticed you’ll be at the event Y in two weeks, and it looks like your company Z does a lot of N. I’m really curious about your opinion on N, do you think you can find 20 minutes to chat?” &lt;/p&gt;

&lt;p&gt;If there is no attendee list, you can do the same thing for the speakers. Thankfully, they’re always public. By the way, try to become one! Identify the list of events you’d like to speak at and regularly submit proposals. If you get chosen as a speaker, ask your company to sponsor your travel. Being a speaker is a great way to meet a lot of interesting people quickly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;You may get invited to speaker-only dinners/parties. I met Martin Fowler in Budapest at an event like that.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;You become recognizable on the floor - people will want to meet you. In some cases, they’ll line up with questions after your talk!&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Also, don’t forget to find time to check in with people you already know! Just a minute with a friendly face goes a long way. &lt;/p&gt;

&lt;h3&gt;
  
  
  Community
&lt;/h3&gt;

&lt;p&gt;You can’t always visit events; travelling can be hard and expensive. But likely, there are people in your community you’d like to connect with. So, just message them! If they’re local, offer to meet for a coffee. If not, offer to hang out over a call. It’s not as creepy as it sounds if you follow &lt;strong&gt;The Most Important&lt;/strong&gt; &lt;strong&gt;Rule&lt;/strong&gt; - offer to help with something. The worst thing that can happen? They’ll ghost you.&lt;/p&gt;

&lt;p&gt;Your community can be anywhere: social media, Slack groups, development mailing lists, open-source maintainers, Reddit, etc. &lt;/p&gt;

</description>
      <category>consulting</category>
      <category>freelance</category>
      <category>indie</category>
    </item>
    <item>
      <title>Automating Essential Eight Compliance Checks with PowerShell</title>
      <dc:creator>ShadowStrike</dc:creator>
      <pubDate>Sun, 26 Apr 2026 22:11:56 +0000</pubDate>
      <link>https://future.forem.com/shadowstrike/automating-essential-eight-compliance-checks-with-powershell-b9g</link>
      <guid>https://future.forem.com/shadowstrike/automating-essential-eight-compliance-checks-with-powershell-b9g</guid>
      <description>&lt;p&gt;&lt;strong&gt;Version 1.0.0&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you work in Australian IT, cybersecurity, or digital forensics, you've encountered the Essential Eight. It's referenced in government procurement requirements, security assessments, incident response frameworks, and organisational policies across both public and private sectors.&lt;/p&gt;

&lt;p&gt;The Australian Signals Directorate's Essential Eight is a set of eight baseline mitigation strategies designed to protect Windows environments against cyber threats. Checking compliance manually is time-consuming and error-prone. In this tutorial, you'll build a PowerShell script that automates Essential Eight audits, produces colour-coded console output, and writes detailed compliance reports to timestamped files.&lt;/p&gt;

&lt;h2&gt;
  
  
  What You'll Build
&lt;/h2&gt;

&lt;p&gt;A PowerShell script called &lt;code&gt;essential_eight.ps1&lt;/code&gt; that checks a Windows system against all eight ASD mitigation strategies and produces a comprehensive audit report showing PASS/FAIL/WARN/INFO status for each control.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;PowerShell 5.1 or later (Windows 10+)&lt;/li&gt;
&lt;li&gt;Administrator privileges recommended (some checks require elevated access)&lt;/li&gt;
&lt;li&gt;No external modules or dependencies&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Understanding the Essential Eight
&lt;/h2&gt;

&lt;p&gt;Before building the script, it's worth understanding what each strategy actually checks. The ASD publishes detailed guidance, but the practical questions are:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Application Control&lt;/strong&gt; — Is there a mechanism (AppLocker, Windows Defender Application Control) preventing unauthorised executables from running?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Patch Applications&lt;/strong&gt; — Are third-party applications kept up to date? Is Windows Update active?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Configure Microsoft Office Macro Settings&lt;/strong&gt; — Are macros restricted to prevent malicious Office documents from executing code?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. User Application Hardening&lt;/strong&gt; — Are legacy and high-risk features (Internet Explorer, Windows Script Host, PowerShell v2) disabled or restricted?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Restrict Administrative Privileges&lt;/strong&gt; — Are admin accounts limited? Is UAC enabled? Are standard users running with least privilege?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Patch Operating Systems&lt;/strong&gt; — Is the OS on a supported and current build?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Multi-Factor Authentication&lt;/strong&gt; — Is MFA enforced, especially for privileged access?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;8. Regular Backups&lt;/strong&gt; — Are backups in place and recent? Are they protected from modification?&lt;/p&gt;

&lt;p&gt;Each strategy has Maturity Levels (ML1, ML2, ML3) in the full ASD framework. This script focuses on ML1 baseline checks — the foundation that must exist before maturity level progression begins.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Complete Script
&lt;/h2&gt;

&lt;p&gt;Here's the full implementation (condensed for readability - full version on GitHub):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Essential Eight Compliance Checker&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Author: ShadowStrike (Strategos)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;param&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Parameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Mandatory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;$false&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$OutputDir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Parameter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Mandatory&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;$false&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;switch&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$Verbose&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Initialise report&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$timestamp&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-Date&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Format&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"yyyyMMdd_HHmmss"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$outputFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Join-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$OutputDir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"EssentialEight_Audit_&lt;/span&gt;&lt;span class="nv"&gt;$timestamp&lt;/span&gt;&lt;span class="s2"&gt;.txt"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$report&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;@()&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Track results&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$passCount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$failCount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$warnCount&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;param&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$Strategy&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$Check&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$Status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;string&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$Detail&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nv"&gt;$color&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;switch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$Status&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"PASS"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Green"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;passCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"FAIL"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Red"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;failCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"WARN"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Yellow"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;warnCount&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="s2"&gt;"INFO"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Cyan"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"[&lt;/span&gt;&lt;span class="nv"&gt;$Status&lt;/span&gt;&lt;span class="s2"&gt;] &lt;/span&gt;&lt;span class="nv"&gt;$Strategy&lt;/span&gt;&lt;span class="s2"&gt; - &lt;/span&gt;&lt;span class="nv"&gt;$Check&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$Detail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;": &lt;/span&gt;&lt;span class="nv"&gt;$Detail&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

    &lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ForegroundColor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$color&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$&lt;/span&gt;&lt;span class="nn"&gt;script&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nv"&gt;report&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;+=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$line&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# STRATEGY 1: APPLICATION CONTROL&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;try&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$appLockerPolicy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-AppLockerPolicy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Effective&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ErrorAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Stop&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$appLockerPolicy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RuleCollections&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-gt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AppControl"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AppLocker Policy"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PASS"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Effective rules configured"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AppControl"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AppLocker Policy"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FAIL"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"No effective rules"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;catch&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AppControl"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AppLocker Policy"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FAIL"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Not configured"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# STRATEGY 2: PATCH APPLICATIONS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$wuService&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;wuauserv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ErrorAction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SilentlyContinue&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$wuService&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$wuService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Running"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Patching"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Windows Update Service"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PASS"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Running"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Patching"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Windows Update Service"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FAIL"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Not running"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Check last update date&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$session&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ComObject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.Update.Session&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$searcher&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateUpdateSearcher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$history&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$searcher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QueryHistory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-First&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$lastUpdate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$history&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Date&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$daysSince&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-Date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$lastUpdate&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$daysSince&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Days&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-le&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Patching"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Last Update"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PASS"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$daysSince&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TotalDays&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;) days ago"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Patching"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Last Update"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FAIL"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;]::&lt;/span&gt;&lt;span class="n"&gt;Round&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$daysSince&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;TotalDays&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;) days ago (outdated)"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# STRATEGY 3: OFFICE MACRO SETTINGS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$excelPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HKCU:\Software\Microsoft\Office\16.0\Excel\Security"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Test-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$excelPath&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nv"&gt;$vbaWarnings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-ItemProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$excelPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;VBAWarnings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;VBAWarnings&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nx"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$vbaWarnings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-or&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$vbaWarnings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MacroSettings"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Excel VBA"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PASS"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Restricted"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MacroSettings"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Excel VBA"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FAIL"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Unrestricted"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# STRATEGY 4: USER APPLICATION HARDENING&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$psv2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-WindowsOptionalFeature&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Online&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-FeatureName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MicrosoftWindowsPowerShellV2Root&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$psv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;State&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Disabled"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hardening"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PowerShell v2"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PASS"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Disabled"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hardening"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PowerShell v2"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FAIL"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Enabled (should be removed)"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$langMode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$ExecutionContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SessionState&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;LanguageMode&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$langMode&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ConstrainedLanguage"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hardening"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PS Language Mode"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PASS"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ConstrainedLanguage"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hardening"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PS Language Mode"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WARN"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FullLanguage (unrestricted)"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# STRATEGY 5: RESTRICT ADMIN PRIVILEGES&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$uacPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\System"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$uacEnabled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-ItemProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$uacPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;EnableLUA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnableLUA&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nx"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$uacEnabled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Privileges"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UAC"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PASS"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Enabled"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Privileges"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"UAC"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FAIL"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Disabled"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# STRATEGY 6: PATCH OPERATING SYSTEMS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$os&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-CimInstance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Win32_OperatingSystem&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$osBuild&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BuildNumber&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$osBuild&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-ge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;19041&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OS Patching"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OS Support"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PASS"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Supported build"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OS Patching"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OS Support"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FAIL"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Legacy build"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# STRATEGY 7: MULTI-FACTOR AUTHENTICATION&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="c"&gt;# Note: Full MFA assessment requires Azure AD audit&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"MFA"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Assessment"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"INFO"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Requires directory-level audit"&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# STRATEGY 8: REGULAR BACKUPS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$vssService&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-Service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;VSS&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$vssService&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Status&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Running"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Backups"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Volume Shadow Copy"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PASS"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Running"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kr"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Backups"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Volume Shadow Copy"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"WARN"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Not running"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="c"&gt;# Write summary&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;`n&lt;/span&gt;&lt;span class="s2"&gt;[AUDIT SUMMARY]"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"  PASS: &lt;/span&gt;&lt;span class="nv"&gt;$passCount&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ForegroundColor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Green&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"  FAIL: &lt;/span&gt;&lt;span class="nv"&gt;$failCount&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ForegroundColor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Red&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;Write-Host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"  WARN: &lt;/span&gt;&lt;span class="nv"&gt;$warnCount&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ForegroundColor&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Yellow&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$report&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Out-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-FilePath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$outputFile&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Encoding&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;UTF8&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  How to Use It
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Troubleshooting: Execution Policy
&lt;/h3&gt;

&lt;p&gt;If you see an error about scripts being disabled or not digitally signed:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 1: Set execution policy&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Set-ExecutionPolicy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ExecutionPolicy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;RemoteSigned&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Scope&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;CurrentUser&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;strong&gt;Step 2: Unblock the file if downloaded&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;Unblock-File&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;\essential_eight.ps1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Windows marks files downloaded from GitHub with Zone.Identifier metadata. Even with RemoteSigned policy, these files are blocked unless digitally signed or explicitly unblocked.&lt;/p&gt;

&lt;p&gt;Check your current policy with &lt;code&gt;Get-ExecutionPolicy -List&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Security Note: Read the Code Before You Run It
&lt;/h3&gt;

&lt;p&gt;This script is not digitally signed. Code signing certificates cost $200-500/year and are unnecessary for open-source scripts where you can read the source code yourself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Before running this or any PowerShell script:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read it completely&lt;/strong&gt; — every line, every function, every registry path&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Understand what it does&lt;/strong&gt; — does it match the stated purpose?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Check for risks&lt;/strong&gt; — network calls, file deletions, credential access&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Apply the &lt;strong&gt;ABC principle&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Assume nothing&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Believe nothing&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Check everything&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A digital signature tells you WHO wrote code, not WHETHER it's safe. You are the final security control. Never execute code you haven't personally reviewed and understood.&lt;/p&gt;


&lt;h3&gt;
  
  
  Basic Audit
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;\essential_eight.ps1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Runs the audit and writes report to &lt;code&gt;EssentialEight_Audit_[timestamp].txt&lt;/code&gt; in the current directory.&lt;/p&gt;
&lt;h3&gt;
  
  
  Specify Output Directory
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;\essential_eight.ps1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-OutputDir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"C:\Compliance\Reports"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h3&gt;
  
  
  Run as Administrator
&lt;/h3&gt;

&lt;p&gt;Some checks require elevated privileges. Right-click PowerShell → "Run as Administrator", then:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;\essential_eight.ps1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  Understanding the Output
&lt;/h2&gt;

&lt;p&gt;The script produces colour-coded console output:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;[PASS]&lt;/strong&gt; (green) - Control meets baseline requirements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;[FAIL]&lt;/strong&gt; (red) - Control does not meet requirements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;[WARN]&lt;/strong&gt; (yellow) - Partial compliance or requires manual review&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;[INFO]&lt;/strong&gt; (cyan) - Informational finding, not a compliance check&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Sample output (example system for demonstration - not real audit data):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ESSENTIAL EIGHT COMPLIANCE AUDIT]
System: WORKSTATION-01

[STRATEGY 1: APPLICATION CONTROL]
[PASS] AppControl - AppLocker Policy: Effective rules configured
[INFO] AppControl - WDAC Policy: Not deployed (AppLocker in use)

[STRATEGY 2: PATCH APPLICATIONS]
[PASS] Patching - Windows Update Service: Running
[PASS] Patching - Last Update: 2026-04-18 - 6 days ago

[STRATEGY 3: OFFICE MACRO SETTINGS]
[PASS] MacroSettings - Excel VBA Warnings: Macros disabled (Value: 3)
[PASS] MacroSettings - Word VBA Warnings: Notification enabled (Value: 4)

[STRATEGY 4: USER APPLICATION HARDENING]
[PASS] Hardening - PowerShell v2: Disabled
[PASS] Hardening - PowerShell Version: v5.1
[WARN] Hardening - PowerShell Language Mode: FullLanguage (unrestricted)
[PASS] Hardening - Script Block Logging: Enabled

[STRATEGY 5: RESTRICT ADMINISTRATIVE PRIVILEGES]
[PASS] Privileges - Current User Role: Running as standard user
[PASS] Privileges - UAC Status: Enabled
[PASS] Privileges - Local Administrators: 2 account(s) - acceptable range

[STRATEGY 6: PATCH OPERATING SYSTEMS]
[INFO] OS Patching - Operating System: Microsoft Windows 10 Pro (Build 19045)
[PASS] OS Patching - OS Support Status: Windows 10 version 2004+ (supported)

[STRATEGY 7: MULTI-FACTOR AUTHENTICATION]
[INFO] MFA - Windows Hello: Credential provider detected
[INFO] MFA - Credential Providers: 3 provider(s) registered
[INFO] MFA - Note: Full MFA assessment requires Azure AD / Active Directory audit

[STRATEGY 8: REGULAR BACKUPS]
[INFO] Backups - Windows Backup Service: Not active (may use third-party)
[PASS] Backups - Volume Shadow Copy: Running
[PASS] Backups - Latest Shadow Copy: 2026-04-23 - 1 days ago

[AUDIT SUMMARY]
  PASS: 15
  FAIL: 0
  WARN: 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The timestamped report file contains the same information in plain text format for archiving.&lt;/p&gt;
&lt;h2&gt;
  
  
  Code Walkthrough
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Strategy 1: Application Control
&lt;/h3&gt;

&lt;p&gt;Checks for AppLocker or Windows Defender Application Control (WDAC):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$appLockerPolicy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-AppLockerPolicy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Effective&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$appLockerPolicy&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;RuleCollections&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-gt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AppControl"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AppLocker Policy"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PASS"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Effective rules configured"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;AppLocker policies can be deployed via Group Policy or locally. The &lt;code&gt;-Effective&lt;/code&gt; parameter returns the combined result of all policies affecting the system.&lt;/p&gt;

&lt;p&gt;For WDAC, the script checks for policy files in &lt;code&gt;C:\Windows\System32\CodeIntegrity\CiPolicies\Active\&lt;/code&gt;.&lt;/p&gt;
&lt;h3&gt;
  
  
  Strategy 2: Patch Applications
&lt;/h3&gt;

&lt;p&gt;Verifies Windows Update service is running and checks last update date:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$session&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;New-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-ComObject&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Microsoft.Update.Session&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$searcher&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CreateUpdateSearcher&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$history&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$searcher&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;QueryHistory&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-First&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$lastUpdate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$history&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Date&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$daysSince&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-Date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$lastUpdate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The Windows Update COM API provides detailed patch history. This check flags systems where the last update was &amp;gt;30 days ago as outdated.&lt;/p&gt;
&lt;h3&gt;
  
  
  Strategy 3: Office Macro Settings
&lt;/h3&gt;

&lt;p&gt;Checks registry settings for Excel and Word macro behaviour:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$excelPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HKCU:\Software\Microsoft\Office\16.0\Excel\Security"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$vbaWarnings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-ItemProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$excelPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;VBAWarnings&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;VBAWarnings&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;VBAWarnings values:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;1&lt;/code&gt; = Enable all macros (dangerous)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;2&lt;/code&gt; = Disable all with notification&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;3&lt;/code&gt; = Disable all except digitally signed&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;4&lt;/code&gt; = Disable all without notification (most secure)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Values 3 and 4 meet Essential Eight ML1 baseline.&lt;/p&gt;
&lt;h3&gt;
  
  
  Strategy 4: User Application Hardening
&lt;/h3&gt;

&lt;p&gt;Multiple checks including PowerShell v2 removal and script block logging:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$psv2&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-WindowsOptionalFeature&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Online&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-FeatureName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;MicrosoftWindowsPowerShellV2Root&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;$psv2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;State&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-eq&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Disabled"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Hardening"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PowerShell v2"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PASS"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Disabled"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;PowerShell v2 lacks modern security features (script block logging, Constrained Language Mode support) and should be removed from all systems.&lt;/p&gt;

&lt;p&gt;Script block logging check:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$scriptBlockPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HKLM:\Software\Policies\Microsoft\Windows\PowerShell\ScriptBlockLogging"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$scriptBlockEnabled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-ItemProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$scriptBlockPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;EnableScriptBlockLogging&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnableScriptBlockLogging&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;When enabled (value = 1), all PowerShell script execution is logged to Windows Event Log, providing audit trails for forensic investigation.&lt;/p&gt;
&lt;h3&gt;
  
  
  Strategy 5: Restrict Administrative Privileges
&lt;/h3&gt;

&lt;p&gt;Checks UAC status and local administrator count:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$uacPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HKLM:\Software\Microsoft\Windows\CurrentVersion\Policies\System"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$uacEnabled&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-ItemProperty&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Path&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$uacPath&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;EnableLUA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;EnableLUA&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="nv"&gt;$admins&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-LocalGroupMember&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Group&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Administrators"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;UAC (User Account Control) prompts for elevation when administrative actions are attempted. Disabling UAC is a common misconfiguration that violates Essential Eight requirements.&lt;/p&gt;

&lt;p&gt;The administrator count check flags systems with excessive admin accounts. Typically, only built-in Administrator and one domain admin account should exist on workstations.&lt;/p&gt;
&lt;h3&gt;
  
  
  Strategy 6: Patch Operating Systems
&lt;/h3&gt;

&lt;p&gt;Verifies OS is on a supported build:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$os&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-CimInstance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Win32_OperatingSystem&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$osBuild&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;BuildNumber&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="kr"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;int&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="nv"&gt;$osBuild&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-ge&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;19041&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="n"&gt;Write-Check&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OS Patching"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"OS Support"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"PASS"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Supported build"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Build 19041 = Windows 10 version 2004, the minimum currently-supported consumer version. Enterprise LTSC versions may have different support timelines.&lt;/p&gt;
&lt;h3&gt;
  
  
  Strategy 7: Multi-Factor Authentication
&lt;/h3&gt;

&lt;p&gt;MFA assessment requires directory-level audit (Azure AD, Active Directory):&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$credProviders&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-ChildItem&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HKLM:\SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;The script checks for Windows Hello and registered credential providers, but full MFA enforcement validation requires checking conditional access policies in Azure AD or examining GPO settings for smart card requirements.&lt;/p&gt;
&lt;h3&gt;
  
  
  Strategy 8: Regular Backups
&lt;/h3&gt;

&lt;p&gt;Checks Volume Shadow Copy Service and recent shadow copies:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$shadows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Get-CimInstance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Win32_ShadowCopy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Where-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;$_&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;VolumeName&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-like&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"*C:*"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$latestShadow&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$shadows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Sort-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;InstallDate&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-Descending&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Select-Object&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-First&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;1&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="nv"&gt;$daysSince&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Get-Date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nv"&gt;$latestShadow&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;InstallDate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Shadow copies are point-in-time snapshots used for System Restore and file recovery. The script flags systems where the latest shadow copy is &amp;gt;7 days old.&lt;/p&gt;
&lt;h2&gt;
  
  
  Real-World Use Cases
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Pre-audit preparation:&lt;/strong&gt; Run this script on sample systems before formal compliance assessments to identify gaps early.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Continuous monitoring:&lt;/strong&gt; Schedule the script as a daily task and email the report to security teams. Track compliance drift over time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Incident response baseline:&lt;/strong&gt; After a security incident, run the script on affected systems to document Essential Eight posture at the time of compromise.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Procurement verification:&lt;/strong&gt; When deploying new workstations, run the audit to verify vendor configuration meets baseline requirements before acceptance.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maturity level progression:&lt;/strong&gt; Use the baseline audit results to identify systems ready for ML2/ML3 implementation.&lt;/p&gt;
&lt;h2&gt;
  
  
  Extending the Script
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;HTML report generation:&lt;/strong&gt; Replace the plain-text output with an HTML file including coloured tables and charts.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Email integration:&lt;/strong&gt; Add &lt;code&gt;Send-MailMessage&lt;/code&gt; to automatically email reports to compliance teams.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remote execution:&lt;/strong&gt; Wrap the script in &lt;code&gt;Invoke-Command&lt;/code&gt; to audit multiple systems from a central management workstation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SIEM integration:&lt;/strong&gt; Parse the output and send findings to a SIEM platform for centralised compliance monitoring.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Remediation actions:&lt;/strong&gt; Add a &lt;code&gt;-Remediate&lt;/code&gt; switch that automatically fixes common failures (enable UAC, remove PowerShell v2, enable script block logging).&lt;/p&gt;
&lt;h2&gt;
  
  
  Maturity Levels Beyond ML1
&lt;/h2&gt;

&lt;p&gt;This script focuses on ML1 (baseline) checks. The ASD Essential Eight Maturity Model has three levels:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maturity Level 1:&lt;/strong&gt; Partly aligned with intent. Baseline controls exist but may not be comprehensive.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maturity Level 2:&lt;/strong&gt; Mostly aligned with intent. Controls cover most assets and attack vectors.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Maturity Level 3:&lt;/strong&gt; Fully aligned with intent. Comprehensive controls across all assets with robust monitoring.&lt;/p&gt;

&lt;p&gt;Progression from ML1 → ML2 → ML3 requires increasingly stringent technical controls and centralised management. For example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Application Control ML1:&lt;/strong&gt; AppLocker with basic rules&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application Control ML2:&lt;/strong&gt; Centralised AppLocker or WDAC with allow-listing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application Control ML3:&lt;/strong&gt; WDAC with cryptographic verification of all executables&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This script provides the ML1 foundation. Assessing ML2/ML3 compliance requires additional checks beyond what local PowerShell can determine (requires SCCM, Intune, or GPO reporting).&lt;/p&gt;
&lt;h2&gt;
  
  
  GitHub Repository
&lt;/h2&gt;

&lt;p&gt;The complete script, sample reports, and this tutorial are available on GitHub:&lt;/p&gt;


&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://assets.dev.to/assets/github-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/ShadowStrike-CTF" rel="noopener noreferrer"&gt;
        ShadowStrike-CTF
      &lt;/a&gt; / &lt;a href="https://github.com/ShadowStrike-CTF/essential-eight-checklist" rel="noopener noreferrer"&gt;
        essential-eight-checklist
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      PowerShell scripts for practical Essential Eight compliance checking on Windows systems.
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;
&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;essential-eight-checklist&lt;/h1&gt;

&lt;/div&gt;

&lt;p&gt;PowerShell scripts for practical Essential Eight compliance checking on Windows systems.&lt;/p&gt;

&lt;/div&gt;
&lt;br&gt;
&lt;br&gt;
  &lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/ShadowStrike-CTF/essential-eight-checklist" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


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

&lt;p&gt;Essential Eight compliance doesn't require expensive enterprise tools. PowerShell provides native cmdlets for checking application control, patching status, user hardening, privilege restrictions, and backup configurations.&lt;/p&gt;

&lt;p&gt;This script packages those checks into a practical audit workflow that runs on any Windows 10/11 system without dependencies. For IT security professionals, compliance teams, and system administrators working in Australian government or enterprise environments, automated Essential Eight auditing is a fundamental operational requirement.&lt;/p&gt;

&lt;p&gt;The value isn't just in the compliance report — it's in the velocity. Running this audit manually would take an experienced admin 30-60 minutes per system. The script completes it in seconds and produces consistent, auditable results.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built by ShadowStrike (Strategos) — where we build actual security tools instead of theatre 🎃.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>powershell</category>
      <category>security</category>
      <category>compliance</category>
      <category>australia</category>
    </item>
    <item>
      <title>Proseflow: Stop Writing Changelogs by Hand (We Built an AI for That)</title>
      <dc:creator>shiplog-bot</dc:creator>
      <pubDate>Sun, 26 Apr 2026 22:03:17 +0000</pubDate>
      <link>https://future.forem.com/shiplogbot/proseflow-stop-writing-changelogs-by-hand-we-built-an-ai-for-that-1c23</link>
      <guid>https://future.forem.com/shiplogbot/proseflow-stop-writing-changelogs-by-hand-we-built-an-ai-for-that-1c23</guid>
      <description>&lt;p&gt;&lt;strong&gt;The Problem:&lt;/strong&gt; Changelogs suck to write. You finish a sprint, merged 47 pull requests, squashed bugs, shipped features, and now you have to sit down and &lt;em&gt;manually&lt;/em&gt; turn all that into a coherent narrative that makes sense to three different audiences: developers who care about technical details, users who care about what changed, and executives who care that &lt;em&gt;something&lt;/em&gt; changed.&lt;/p&gt;

&lt;p&gt;It's boring. It's repetitive. It's the kind of task that makes you question your life choices.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;Proseflow&lt;/strong&gt; because I was tired of this. And honestly? So was an AI agent I set loose on the problem.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Proseflow?
&lt;/h2&gt;

&lt;p&gt;It's a free, open-beta AI changelog generator that connects directly to your GitHub repository. Pick your repo, pick your date range, hit a button, and get back three different versions of your changelog—one for developers, one for users, and one for executives—ready to copy, download, or ship wherever you need it.&lt;/p&gt;

&lt;p&gt;No more staring at a blank document. No more wondering how to phrase "we fixed the thing that was broken." No more choosing between technical accuracy and readability.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Step 1: GitHub OAuth&lt;/strong&gt;&lt;br&gt;
Click "Sign in with GitHub." We use NextAuth to handle the OAuth flow. No passwords, no sketchy API tokens sitting in your .env file. Just your GitHub account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 2: Pick Your Repo&lt;/strong&gt;&lt;br&gt;
Select from your repositories (public or private—we can access both if you grant permissions).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 3: Pick Your Date Range&lt;/strong&gt;&lt;br&gt;
Choose when you shipped the last version and when you're shipping the next one. We'll grab all the commits and PRs in between.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Step 4: Let AI Do the Thing&lt;/strong&gt;&lt;br&gt;
We send the commit history to OpenAI's GPT-4o-mini, which generates three distinct versions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Developer&lt;/strong&gt;: Technical, includes commit hashes, breaking changes, API updates, performance improvements&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User-Friendly&lt;/strong&gt;: Plain English, focuses on what users can &lt;em&gt;do&lt;/em&gt; differently, drops the jargon&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Executive&lt;/strong&gt;: High-level impact, mentions new capabilities and business value, keeps it brief&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Step 5: Copy, Download, Ship&lt;/strong&gt;&lt;br&gt;
Click to copy any version to your clipboard or download as markdown. Paste into your README, email, release notes—wherever.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Tech Stack
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Frontend &amp;amp; Backend:&lt;/strong&gt; Next.js. Simple, fast, one codebase.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auth:&lt;/strong&gt; NextAuth with GitHub OAuth. You authenticate, we get read access to your repos, we keep things secure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The AI Magic:&lt;/strong&gt; OpenAI's GPT-4o-mini. It's fast, affordable, and surprisingly good at understanding commit messages and turning them into coherent prose across different tones.&lt;/p&gt;

&lt;p&gt;The whole thing runs on Vercel. Deployment is automatic. It's 2025. We're not running servers in our garage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Exists
&lt;/h2&gt;

&lt;p&gt;Changelogs are important. They're how you communicate what you've shipped. But they're &lt;em&gt;boring&lt;/em&gt; to write, so people either skip them, phone them in, or spend hours crafting the perfect version.&lt;/p&gt;

&lt;p&gt;We wanted to automate the grunt work. Let you spend your time building, not writing about building.&lt;/p&gt;

&lt;p&gt;It's free because we built it as a proof of concept (with some help from an autonomous AI agent, which is a fun story for another day). We want to see if it's useful. If it is, we'll keep it free or find a sustainable model that doesn't require a subscription to generate a changelog.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try It
&lt;/h2&gt;

&lt;p&gt;Head over to &lt;strong&gt;&lt;a href="https://proseflow-v1.vercel.app" rel="noopener noreferrer"&gt;https://proseflow-v1.vercel.app&lt;/a&gt;&lt;/strong&gt; and give it a shot.&lt;/p&gt;

&lt;p&gt;It takes 2 minutes to generate your first changelog. If it saves you 30 minutes of writing, that's a win.&lt;/p&gt;

&lt;p&gt;If it's broken, tell me. If it's useful, tell other people. If you have ideas for what it should do next, I'm listening.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Status:&lt;/strong&gt; Free, in beta, actively maintained. Built by an indie dev (with AI assistance) who got tired of writing changelogs.&lt;/p&gt;

</description>
      <category>showdev</category>
      <category>opensource</category>
      <category>ai</category>
      <category>github</category>
    </item>
  </channel>
</rss>
