今日已更新 329 条资讯 | 累计 40774 条内容
关于我们

标签:#net

找到 480 篇相关文章

AI 资讯

Introduction to the Cloud-Native World with Azure Kubernetes Services (AKS) - Series Part 2

Digital transformation has led companies to organize their infrastructure and development processes in entirely new ways. To address the challenges of modern cloud-native applications, concepts like Platform Engineering are gaining increasing importance. Especially in environments using Azure Kubernetes Services (AKS) , platform engineering plays a crucial role in efficiently managing and scaling containerized applications. What is Platform Engineering and Why is it Important? Platform engineering is the process of designing, implementing, and managing internal platforms that provide developers with a stable and efficient environment. These platforms bundle all the necessary resources and services to ensure smooth development and operation of applications. A well-developed platform engineering team ensures that recurring tasks are automated, allowing developers to focus on writing code without dealing with the underlying infrastructure. In a container environment like AKS, automation and standardization are critical. Platform engineering provides the framework to simplify these complex workflows. How Does Platform Engineering Support AKS Deployments? A key advantage of platform engineering is the ability to standardize the entire lifecycle of applications—from development to testing and deployment. When working with AKS, the main task of the platform engineering team is to create a seamless and scalable environment for container orchestration. Here are some key aspects of how platform engineering supports AKS: Standardizing and Automating Deployments Platform engineering enables the automation of Kubernetes cluster deployments in AKS using best practices and tools such as Infrastructure as Code (IaC) (e.g., Terraform or Azure Resource Manager templates). This automation reduces human errors and accelerates the time needed to deploy applications in production environments. Self-Service Platforms for Developers A well-designed platform engineering team builds self-ser

2026-08-10 原文 →
AI 资讯

Nodes and Networks: How Blockchains Actually Stay Decentralized

When someone says "Bitcoin has over 15,000 nodes worldwide," they mean 15,000+ independent computers are each running Bitcoin software and each maintaining their own full copy of the blockchain. No server owns the truth. Every node checks it for itself. That single fact — every node independently verifies every transaction and block against protocol rules — is the reason blockchains don't need a central authority. If one node tries to cheat, the rest simply ignore it. There's no admin account to compromise because there's no admin. Not All Nodes Do the Same Job Full Node Downloads and stores the entire blockchain, every block since genesis, and independently validates everything against consensus rules. Highest security ~500 GB for Bitcoin ~1 TB for Ethereum This is the backbone of network security. A full node doesn't trust anyone's summary of the chain; it recomputes validity itself. Light Node (SPV) Stores only block headers, not full transaction data. Uses Merkle proofs and relies on full nodes to verify transactions. Low storage, ~50 MB Trusts full nodes for verification What most mobile wallets run Mining/Validator Node A full node that also participates in block creation. Miners (Proof of Work) solve computational puzzles; validators (Proof of Stake) stake cryptocurrency as collateral. Both earn rewards for securing the network. Creates new blocks Earns rewards Requires specialized hardware (PoW) or capital at stake (PoS) Archive Node Everything a full node stores, plus historical state at every block height. Complete history ~15+ TB for Ethereum Used by explorers, analytics platforms, and enterprise tooling Why Peer-to-Peer Instead of Client-Server A traditional web service is client-server: your browser requests data from a company's servers. If those servers go down, the service is unavailable. That's a single point of failure by design. Blockchain networks use peer-to-peer (P2P) architecture instead. Every participant is simultaneously a client and a serv

2026-08-10 原文 →
AI 资讯

Cedar could stop one bad tool call. Dogwood stops bad sequences.

AWS launched Dogwood this week — an open-source policy language (Apache 2.0) for AI agent runtime verification. It extends Cedar, AWS's existing authorization language (now a CNCF sandbox project), with something Cedar fundamentally can't do: reason about sequences of actions over time. "Point-in-time decisions make sense for many forms of access control, but when agents compose multiple actions into longer workflows, the sequence itself becomes something teams want to govern." That's the gap Dogwood fills. What Cedar couldn't do Cedar is stateless. You give it a request — principal, action, resource, parameters — and it returns allow or deny. Given the same request, Cedar always returns the same answer, regardless of what happened five minutes ago. That's a useful property for analysis, but it's a blind spot for agents. Consider: an agent is restricted to transferring no more than $5,000 per hour. If Cedar only evaluates the current request against completed transfers, the agent can fire off three concurrent $2,000 requests before any of them finish. Each looks fine in isolation. The total blows the limit. Dogwood has the event history. It counts all transfer requests — including those currently in-flight — so the third $2,000 request gets denied even before the first two complete. What Dogwood adds Dogwood introduces temporal conditions that examine earlier tool calls and their results. You can: Check whether an event occurred — e.g., was approval granted for this exact stock/quantity in the last hour? Count calls in a time window — rate limiting across concurrent requests Count distinct values — e.g., how many unique payment recipients this session Sum values — total transferred, total refunded The stock trading example from AWS is the clearest illustration: an agent may only sell shares if an approval tool returned a positive response for that stock and share count within the previous hour. That approval is a separate event the policy engine finds in the agent's

2026-08-09 原文 →
AI 资讯

Unary gRPC on Reactor Netty: Event Loop Serialization, Trailers, and Cancellation

With protocol values and message framing complete, Stage 2 delivered the first end-to-end call: plaintext h2c unary RPC. This is already on main , and Stage 3 and Stage 4 subsequently completed all four RPC cardinalities on the same transport primitive. Previous: Building a Leak-Safe gRPC Frame Decoder on Reactor Netty Method Descriptor Is Where Protocol Meets Types A method requires a precise service name, method name, cardinality, and request/response marshallers: var echo = new GrpcMethod <>( "testing.EchoService" , "Echo" , GrpcMethod . Cardinality . UNARY , new ProtobufMarshaller <>( StringValue . parser ()), new ProtobufMarshaller <>( StringValue . parser ())); The generated path must be: /testing.EchoService/Echo The service registry matches by exact full path. An unknown path returns UNIMPLEMENTED ; registering the same path twice fails immediately when building the service definition. Server Validates Protocol Before Subscribing to Business Logic ReactorGrpcServer uses Reactor Netty h2c: DisposableServer bound = HttpServer . create () . host ( host ) . port ( port ) . protocol ( HttpProtocol . H2C ) . handle ( handler: : handle ) . bindNow ( Duration . ofSeconds ( 10 )); Incoming requests are validated in order: HTTP method must be POST; content-type must be application/grpc or application/grpc+... ; te must declare trailers; path must exist; currently only unary cardinality is allowed; metadata and message size must not exceed limits. Only after validation passes does it create a GrpcCallContext and subscribe to the request body, preventing invalid requests from entering the business handler. HTTP 200 Does Not Mean RPC Success The server writes a compatible content-type first; the final status comes from trailing headers: response . status ( 200 ) . header ( HttpHeaderNames . CONTENT_TYPE , "application/grpc+proto" ); response . trailerHeaders ( trailers -> { GrpcException error = terminal . get (); if ( error == null ) { writeStatus ( trailers , GrpcStatu

2026-08-09 原文 →
AI 资讯

Kubernetes Secrets Are Just Base64 Not Encryption. Here's What That Actually Means

If you've run Kubernetes for more than a day, you've seen this: apiVersion : v1 kind : Secret metadata : name : db-credentials type : Opaque data : username : YWRtaW4= password : c3VwZXJzZWNyZXQ= And somewhere in the back of your mind you filed it under "encrypted credentials." It isn't. Those values are Base64, and Base64 is encoding, not encryption. YWRtaW4= is just admin written in a different alphabet — reversible instantly, by anyone, with no key. This trips up an astonishing number of teams, so let's clear it up for good. Prove it in one command kubectl get secret db-credentials -o jsonpath = '{.data.password}' | base64 --decode # supersecret No key. No password. No "decryption." Base64 is a binary-to-text encoding — its entire job is to represent arbitrary bytes using a safe 64-character alphabet so they survive transport and storage in text-based systems (etcd, YAML, JSON, HTTP headers). Kubernetes encodes Secret data values purely so binary values (certs, keys, gzip blobs) can live inside a YAML/JSON object. That's it. Security was never the point. If you want to eyeball a whole Secret at once instead of decoding fields one by one, I built a small in-browser tool for exactly this — paste the YAML and it decodes every data: value locally (nothing is uploaded): Kubernetes Secret Decoder . (Disclosure: it's my free, no-ads tool.) data vs stringData A quick related gotcha: data expects Base64 , but stringData expects plain text and Kubernetes Base64-encodes it for you on write: stringData : password : supersecret # plain text; k8s encodes it into data.password Both end up identically un-secret at rest. So what actually protects a Secret? Base64 gets you nothing here. Real protection is layered: Encryption at rest for etcd — configure a KMS provider (AWS/GCP/Azure KMS) or at minimum aescbc / secretbox via an EncryptionConfiguration . Without this, Secrets sit in etcd Base64-only. Sealed Secrets (Bitnami) — encrypt secrets before they hit Git; only the in-cluster

2026-08-09 原文 →
AI 资讯

Why I Didn’t Build a Custom VPN App: What WireGuard Gave Me and Where the Real Problems Started

Lessons from building a small VPN service around standard WireGuard clients instead of a proprietary app When you look at a commercial VPN product, the app seems to be the product: a polished interface, a country list, and a large Connect button. I chose the opposite approach. Instead of building another VPN client, I decided to give users a standard WireGuard configuration that they could import into an existing client. That decision removed a lot of client-side work — but it also exposed where the real complexity of a VPN service actually lives. Why build another app if WireGuard already has one? The usual commercial VPN flow is straightforward: install the vendor's app, sign in, choose a location, and connect. A proprietary client can manage server selection, subscriptions, kill switches, automatic reconnects, diagnostics, updates, and support in one place. But for a small service with one or a few locations, I had to ask a more basic question: do I really need to build and maintain a separate Windows, macOS, Android, and iOS client just to establish a WireGuard tunnel? WireGuard already has mature clients across the major desktop and mobile platforms. A user can import a configuration file or scan a QR code and get a normal VPN toggle. On paper, that looked like a very attractive tradeoff: less client code, fewer update mechanisms, fewer installers, and a smaller attack surface to maintain. What I underestimated was that the app was never going to be the hardest part. A .conf file is not just a settings file The first architectural lesson was simple but important: a WireGuard configuration is effectively a credential. It contains the client's private key. A QR code that represents the same configuration contains the same sensitive material in another form. That immediately creates product problems that have nothing to do with the tunnel itself. How do you show the configuration safely? What happens if the user loses it? Can you issue a replacement without leavin

2026-08-09 原文 →
产品设计

Buc-ee’s dodges John Oliver to sue another small business

Buc-ee's became something of a viral sensation during the World Cup, but it has a troubling history of suing small gas stations and convenience stores. On a recent episode of Last Week Tonight, John Oliver literally begged the company to sue him for selling merch featuring his squirrel mascot, Mr. Nutterbutter, with branding that reads […]

2026-08-08 原文 →
AI 资讯

Self-Hosted SSO for 25 Services: Authelia OIDC on Kubernetes

Originally published at woitzik.dev Disclosure: This post contains Amazon affiliate links (marked with *). If you buy through them, I earn a small commission at no extra cost to you. I only link gear I actually own and use daily. Every internal service in my homelab goes through the same authentication gate: Authelia. Proxmox, PBS, Grafana, ArgoCD, Headscale, ArgoCD, Uptime Kuma, Paperless, Nextcloud — 25+ web services, one login, one session, one set of access rules. The OIDC provider, the Postgres backend, the session store, and the secrets are all running inside k3s, backed by CNPG, Redis, and Vault. This article is the full implementation: how the pieces fit together, why certain design decisions were made, and the specific bugs that bit me along the way. View the complete homelab infrastructure source on GitHub 🐙 The Architecture Authelia runs as a Kubernetes Deployment in the apps namespace, protected by the same default-deny NetworkPolicy that applies to everything else. It has three dependencies: PostgreSQL — CNPG-managed postgres-authelia cluster in the database namespace Redis — session store, ephemeral (no persistence needed) Vault — hmac_secret, OIDC private keys, JWT secrets, session secrets The Traefik ForwardAuth middleware sits in front of every service. When a request hits Traefik, the middleware sends a verification request to Authelia's /api/verify endpoint. Authelia checks the session cookie, validates the OIDC token if applicable, and returns a 200 (allowed) or 401 (redirect to login). # kubernetes/apps/authelia/middleware.yml apiVersion : traefik.io/v1alpha1 kind : Middleware metadata : name : authelia namespace : apps spec : forwardAuth : address : " http://authelia.apps.svc.cluster.local:9999/api/verify" trustForwardHeader : true authResponseHeaders : - Remote-User - Remote-Groups - Remote-Email Every IngressRoute that needs protection adds middlewares: [{name: authelia}] . Services that need API-level protection (not browser-based) use OIDC

2026-08-08 原文 →
AI 资讯

AIOps Agents for Kubernetes Human-in-the-Loop Remediation on GCP

The Problem with Fully Autonomous Remediation Every platform team eventually asks the same question: can we let something automatically fix production when it breaks? The instinct to say yes is understandable incidents at 3 a.m. are expensive, and a lot of Kubernetes failures follow recognizable patterns. But fully autonomous remediation has a bad failure mode: when the agent is wrong, it's wrong fast, and it's wrong at scale. AIOps agents for Kubernetes solve this by splitting the problem in two: let the agent do the work of detection, correlation, and proposal the parts humans are slow and inconsistent at and keep a human as the final decision-maker for anything with real consequences. This is the human-in-the-loop (HITL) model, and on Google Cloud it maps cleanly onto existing primitives: GKE for the runtime, Cloud Monitoring/Logging for signal, IAM and Kubernetes RBAC for guardrails, and Vertex AI or a self-hosted model for the reasoning layer. What the Agent Actually Does Strip away the buzzwords and an AIOps agent for Kubernetes does four things on a loop: Watch — consume events, metrics, and logs from the cluster and surrounding GCP services Correlate — connect a symptom (say, elevated 5xx rate) to a likely cause (a bad rollout, a starved node, an expired credential) Propose — generate one or more candidate remediations, each with a confidence score and an estimate of blast radius Act or Ask — execute directly if the action is pre-approved as low-risk, otherwise route to a human for a decision The engineering effort is disproportionately in steps 2 and 4. Step 2 (correlation) requires the agent to reason over multiple, often noisy signal sources rather than pattern-match a single metric. Step 4 (the human gate) requires a review surface good enough that a tired on-call engineer can make a correct decision in seconds, not minutes. Core Signals on GKE The Approval Gate, Concretely The human-in-the-loop gate is usually a chat-based approval flow, since on-call e

2026-08-08 原文 →
AI 资讯

Building a Leak-Safe gRPC Frame Decoder on Reactor Netty

This is the second article in my grpc-reactor series. The first article explains why I chose to build the runtime directly on Reactor Netty and where its compatibility boundary sits. This article moves one layer down into the Stage 1 protocol implementation: the frame decoder that every RPC shape relies on. gRPC protobuf messages are not written directly as raw bytes into HTTP/2 DATA frames. Every message starts with a five-byte envelope: byte 0 bit 0 indicates compression; bits 1-7 must be zero bytes 1-4 unsigned big-endian payload length byte 5..n protobuf message, or its compressed representation Encoding this envelope is straightforward. The difficult part is decoding it without assuming that one input buffer contains one complete frame. HTTP/2, TCP, and Reactor Netty do not promise that buffer boundaries will line up with gRPC message boundaries. This post describes the Stage 1 protocol layer. The project has since progressed beyond it, but the ownership and bounded-decoding rules introduced here remain the foundation for the later transport stages. Encoding Must Define Ownership The contract of GrpcFrameCodec.encode is deliberately explicit: the returned frame and the input message have independent lifetimes. Encoding must not move the input reader index or release the input buffer. The implementation currently copies the readable bytes into a byte array before applying compression: public static ByteBuf encode ( ByteBufAllocator allocator , ByteBuf message , GrpcCompression . Codec compression ) { boolean compressed = ! compression . name (). equals ( "identity" ); byte [] payload = new byte [ message . readableBytes ()]; message . getBytes ( message . readerIndex (), payload ); if ( compressed ) { payload = compression . compress ( payload ); } return allocator . buffer ( GrpcFrameCodec . HEADER_SIZE + payload . length ) . writeByte ( compressed ? 1 : 0 ) . writeInt ( payload . length ) . writeBytes ( payload ); } This is not a zero-copy implementation, and

2026-08-08 原文 →
AI 资讯

Building an offline-first travel app in .NET MAUI (on-device OCR, currency & maps, no backend)

A build note from Horizon Software , a one-person Android studio. WanderWallet is a travel budget app, and the whole thing runs on the phone: no account, no backend, no cloud. Here's how the parts that look like they need a server actually work without one. The one constraint that shaped everything WanderWallet has a single non-negotiable rule: it has to work with no signal. You're three countries into a trip, your phone's in airplane mode to dodge roaming charges, and you still need to know whether you're on budget. That one requirement quietly makes most of the architectural decisions for you — no login, no server round-trips, and every feature that would normally lean on a cloud API has to earn its keep another way. The stack is deliberately boring: .NET MAUI (Android-first), CommunityToolkit.Mvvm , sqlite-net-pcl for storage, and SkiaSharp for anything I draw myself. Everything the app records lives in a local SQLite database on the device and nowhere else. "Backup" is a file you export and keep — there's no server to back up to . The three features people assume need a backend turned out to be the most interesting to build, precisely because they don't. 1. Currency conversion that survives airplane mode A travel budget app that can't convert currencies offline is useless at exactly the moment you need it. So rates aren't fetched on demand. Whenever the app happens to have a connection it refreshes exchange rates for ~155 currencies and caches the whole table locally . From then on every conversion is local arithmetic — a connection only ever buys you a fresher table, never the ability to convert. The design decision that took me longest to get right: capture the conversion immutably, at entry time. Each expense stores the original amount, its original currency, the converted home-currency amount, and the exact rate used — and that rate is never recalculated: public class Expense { public double Amount { get ; set ; } // in OriginalCurrency public string Origina

2026-08-08 原文 →
AI 资讯

The Anatomy of IPv4 Address

I used to think IPv4 addresses were just random numbers until recently. It blew my mind when I started digging and understanding that they have an anatomy where every number after the dot means something very important. Note: To understand what IP addresses are, please consult this post because I won't be going over them here. IP Addresses: Digital Connectivity What is IPv4 in the First Place? IPv4 is short for Internet Protocol version 4 . As you might have already realized, it's the 4th version of the early test designs during the development of the Internet Protocol in test labs in the 1970s. The first real release, v4, came out in 1981 in a public document called RFC 791. RFC 791: STD 5: Internet Protocol IPv4 is an Internet Protocol that's written in what's called Dotted Decimal Notation (e.g., 172.17.0.3 ), where each portion is separated by a dot, and these portions are called octets. For example, 172 is the first octet and 17 is the second octet (more on this later). Before We Explore What Octets Are, Let's Take a Stroll to the Basics of Binary (Simplified) Have you ever wondered why your computer or phone requires electricity to function? Though electricity can be used as a raw power source for things like fans or speakers, where it's converted into other forms of energy such as movement or sound, it's good to know that electricity can function differently in your computer's RAM or SSD. Inside your computer are billions of extremely tiny transistors. These transistors form circuits that can create and maintain different electrical states, which the computer interprets as 0s and 1s. In simple terms, 0 represents the absence of the electrical state (OFF), while 1 represents its presence (ON). These states, which we represent with 0s and 1s, are called binary digits (or simply bits). 1 bit has the possibility of representing either 0 or 1, which doesn't represent much information, and that's where multi-bits come in. Every additional bit doubles the number of

2026-08-07 原文 →
AI 资讯

Introduction to the Cloud-Native World with Azure Kubernetes Services (AKS) - Series Part 1

In today's digital world, businesses face the challenge of developing, deploying, and scaling applications faster and more efficiently. One of the key technologies supporting this transformation is container technology. What are Containers and Why Are They Important? Containers allow applications to be packaged into lightweight, self-contained, and portable units that can run consistently in any environment—from a local development machine to a cloud platform. This reduces dependencies and significantly simplifies application deployment and scalability. Unlike virtual machines (VMs), containers share the operating system kernel, making them more resource-efficient. This leads to higher efficiency and allows businesses to run more applications on the same infrastructure. Introduction to Kubernetes: Orchestration of Containers While containers represent a revolutionary approach to developing and running applications, it’s not enough to simply have containers. Once applications consist of dozens or hundreds of containers, managing, orchestrating, and scaling them becomes critical. This is where Kubernetes comes in. Kubernetes is the world’s most widely used container orchestration platform. It enables the automatic deployment, scaling, and management of containerized applications in clusters. With Kubernetes, companies can ensure their applications are always available, automatically recover from failures, and roll out new versions without downtime. Azure Kubernetes Services (AKS): Kubernetes in the Cloud Azure Kubernetes Services (AKS) is Microsoft’s fully managed Kubernetes solution. With AKS, businesses benefit from simplified Kubernetes deployment by offloading infrastructure management to Microsoft. This means you can focus on developing and scaling your applications while AKS simplifies the management and maintenance of Kubernetes clusters. Benefits of AKS: Fully managed: AKS takes care of the management and patching of Kubernetes, allowing businesses to focus on

2026-08-07 原文 →
AI 资讯

User Connectivity: Making the System Scale with Event Hub Partitions, ACA, and KEDA

Part 3 of the User Connectivity Architecture series. Introduction The first post in this series described the pattern: a heartbeat on a timer, an Event Hub, a worker writing sessions into Redis, and Redis key expiration driving facility online/offline status. One detail matters later. The heartbeat interval is not hard-coded in the client. The API tells the client when to call next, and the default is 30 seconds. The second post covered two years of running that in production. This post is about the month it stopped working. In January 2026 our heartbeat traffic went from boring to terrifying and stayed there for about four weeks. This is the story of what broke, why the original design had a ceiling we never noticed, and the changes that fixed it: more Event Hub partitions, Azure Container Apps, and KEDA . The Storm A normal day looked like this: 51,000-58,000 heartbeats per hour , hour after hour Roughly 15-16 events per second at idle Flat, predictable, forgettable On January 5, around 7:00 AM PST , it stopped being flat. Time (PST) Heartbeats/hour Baseline ~57,000 12:00 PM 80,005 1:00 PM 216,351 5:00 PM 343,480 9:00 PM 466,760 That is eight times normal event volume in a single hour, and it was still climbing. Events were only half the story. SignalR connection counts told the other half. At the worst of it we were holding roughly eleven times the connections we normally maintain, and every one of those was a browser session we had to track, keep alive, and report status for. It did not spike and recover. It stayed elevated for weeks while we hunted for the cause. When we finally found it, the answer was almost funny: 507 zombie sessions that never ended, running months-old cached client code, and a single user account responsible for 33% of all our token API traffic . One account. Eight times the load. Four weeks. What Eight Times Load Actually Did Here is the part that matters, and it has nothing to do with the number itself. Our Event Hub had one partition. I

2026-08-06 原文 →
AI 资讯

Building Proxify: A Reverse Proxy in Go

A reverse proxy sits between clients and one or more upstream services. Instead of clients communicating directly with your application, every request first passes through the proxy before being forwarded to an upstream. Mature reverse proxies such as Nginx, Envoy, and HAProxy do much more than simply forward requests. They perform tasks such as load balancing, health checks, rate limiting, metrics collection, and much more. I wanted to better understand how some of these concepts work in practice, so I built a reverse proxy in Go. Along the way I implemented request forwarding, multiple load-balancing strategies, health checks, circuit breakers, rate limiting, request logging, metrics, and graceful shutdown. If you'd like to explore Proxify as we go, you can find the project here: https://github.com/Rahmannugar/proxify Table of Contents Request Lifecycle Project Structure Configuration Reverse Proxy Load Balancing Health Checks Circuit Breakers Middleware Graceful Shutdown Running Proxify with Docker 1. Request Lifecycle At a high level, every request follows the same path through the reverse proxy. A client sends an HTTP request to Proxify instead of communicating directly with an upstream service. Proxify receives the request, selects a healthy upstream using the configured load-balancing strategy, forwards the request, waits for the upstream's response, and finally returns that response to the client. Client │ ▼ +---------------+ | Proxify | +---------------+ │ Select Healthy Upstream │ ┌───────┴────────┐ ▼ ▼ Upstream A Upstream B │ ▼ HTTP Response │ ▼ Client Although the overall flow is straightforward, every step introduces additional considerations. Which upstream should receive the next request? What happens when an upstream becomes unhealthy? How can requests be distributed efficiently across multiple upstreams? How do we prevent a failing upstream from continuing to receive traffic? The remainder of this article answers those questions by gradually buildin

2026-08-06 原文 →
开源项目

From Projects to Products: Turning Platforms into Products People Use

Having a platform is not enough; the real challenge is ensuring that it is understandable, usable, and actually adopted by its users. A capability is done when it can be reliably used by others. To evaluate progress, you can ask yourself “Is this being used?” and “Does it reduce friction for users?” This can help align development work with actual user value rather than delivery, By Ben Linders

2026-08-06 原文 →