<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>StackOnward</title><link>https://stackonward.com/</link><description>Recent content on StackOnward</description><generator>Hugo</generator><language>en-US</language><atom:link href="https://stackonward.com/index.xml" rel="self" type="application/rss+xml"/><item><title>What Is Jev AI? It Picks an Agent’s Next Move</title><link>https://stackonward.com/posts/jev-system-one-open-source-projects/</link><pubDate>Sat, 19 Sep 2026 13:15:00 +0800</pubDate><guid>https://stackonward.com/posts/jev-system-one-open-source-projects/</guid><description>&lt;p&gt;Jev AI has had a ridiculous week. &lt;a href="https://github.com/browser-use/jev-ultrafast" target="_blank" rel="noopener"&gt;&lt;code&gt;jev-ultrafast&lt;/code&gt;&lt;span class="sr-only"&gt;(opens in a new tab)&lt;/span&gt;&lt;/a&gt; passed 6,400 GitHub stars less than three days after it was created, while Jev projects for Mac and Android control, coding-agent routing, and code review appeared one after another. &lt;a href="https://typesafe.ai/" target="_blank" rel="noopener"&gt;Jev&lt;span class="sr-only"&gt;(opens in a new tab)&lt;/span&gt;&lt;/a&gt; is TypeSafe’s model for questions where software can define the possible answers in advance. It returns a choice, score, or probability instead of writing an open-ended response.&lt;/p&gt;
&lt;h2 id="what-does-jev-return-instead-of-text"&gt;What does Jev return instead of text?&lt;/h2&gt;
&lt;p&gt;A generative model often explains the page or Git diff before answering. Software may need only one action: which button should it click next? Once the model finishes writing, the program still has to extract that action from the response.&lt;/p&gt;</description></item><item><title>Go Live with Payments: From Sandbox Keys to the First Real Order</title><link>https://stackonward.com/posts/payment-sandbox-to-production-launch/</link><pubDate>Fri, 11 Sep 2026 05:24:00 +0800</pubDate><guid>https://stackonward.com/posts/payment-sandbox-to-production-launch/</guid><description>&lt;p&gt;The checkout page already uses a live key, but the subscription still cites a test Price. After the customer pays, the production webhook fails signature checks because it still uses the sandbox secret. Mixed config does not fail together: some errors stop before payment, others wait until money has moved, then order, entitlements, and finance stop in different states.&lt;/p&gt;
&lt;p&gt;The cutover order is: fence entries that create captures and refunds; attach account, keys, Price, webhook, domain, workers, and finance to live together; use read-only requests, test events, and return-URL checks to catch environment errors; then open the first real order under provider rules. If that first order fails, close new transactions. Webhooks, lookups, and reconciliation keep running.&lt;/p&gt;</description></item><item><title>Why Payment, Attempt, and Transaction Must Stay Separate</title><link>https://stackonward.com/posts/payment-attempt-transaction-separation/</link><pubDate>Fri, 11 Sep 2026 05:22:00 +0800</pubDate><guid>https://stackonward.com/posts/payment-attempt-transaction-separation/</guid><description>&lt;p&gt;Payment, Attempt, and Transaction amounts cannot be added together. Payment stores owner and target amount for one local collection. Attempt stores the conditions and progress of one accepted operation. Transaction stores the result of one channel money action. A failed try followed by a succeeded retry can leave two execution records and only one amount received.&lt;/p&gt;
&lt;p&gt;Keeping only the latest state on one row lets a retry overwrite the last failure, lets a refunded net hide the original capture, and leaves a back-office “success” that cannot say whether money was captured. Separate records answer how many times the customer tried, how much the channel actually captured, and how much was later returned.&lt;/p&gt;</description></item><item><title>Payment Domain Model: Order, Payment, Attempt, and Transaction</title><link>https://stackonward.com/posts/payment-domain-object-model/</link><pubDate>Fri, 11 Sep 2026 05:20:00 +0800</pubDate><guid>https://stackonward.com/posts/payment-domain-object-model/</guid><description>&lt;p&gt;A purchase can capture successfully while the workspace stays “provisioning.” Charging again does not fix that. Find the fulfillment record for the original order, and why it did not finish.&lt;/p&gt;
&lt;p&gt;A payment domain model stores sale terms, execution, and money results as separate identities. Quote freezes the payable amount. Order stores what both sides confirmed. CheckoutSession and execution records manage the collection process. Payment is the local payment. Transaction stores the result of a specific money action. Provider objects attach through their own identity map. Refunds and fulfillment then point at confirmed purchase and money records. One &lt;code&gt;paid&lt;/code&gt; field cannot replace those links.&lt;/p&gt;</description></item><item><title>How to Troubleshoot a Slow Website: DNS, TTFB, and LCP</title><link>https://stackonward.com/posts/slow-website-troubleshooting/</link><pubDate>Fri, 11 Sep 2026 05:18:00 +0800</pubDate><guid>https://stackonward.com/posts/slow-website-troubleshooting/</guid><description>&lt;p&gt;How to troubleshoot a slow website starts with the URL, the time window, and who is affected. Split one load into DNS, connection, TLS, waiting for the first byte, body download, and rendering. Hosting, CDN, database, and frontend changes only help after that interval has an owner.&lt;/p&gt;
&lt;figure&gt;
 &lt;img src="https://stackonward.com/images/posts/slow-website-troubleshooting/en/performance-path.svg" alt="Performance path from confirming URL and time, through curl phase timings, then to CDN and origin when TTFB is high or to browser rendering when HTML is already fast" loading="lazy" decoding="async"&gt;
 &lt;figcaption&gt;curl locates network and first-byte time. Origin metrics and the browser timeline explain the remaining wait.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="start-troubleshooting-a-slow-website-from-the-affected-url"&gt;Start troubleshooting a slow website from the affected URL&lt;/h2&gt;
&lt;p&gt;Record the incident before opening a profiler:&lt;/p&gt;</description></item><item><title>What Happens When You Type a URL in the Browser</title><link>https://stackonward.com/posts/domain-request-flow/</link><pubDate>Fri, 11 Sep 2026 05:18:00 +0800</pubDate><guid>https://stackonward.com/posts/domain-request-flow/</guid><description>&lt;p&gt;What happens when you type a URL in the browser is a sequence of hops, not a round trip to one origin. The request usually travels through DNS, a TCP or QUIC connection, TLS, a CDN or reverse proxy, and only then the application. The HTML that comes back is not the end; the browser still fetches styles, scripts, fonts, and images. &lt;a href="https://developer.mozilla.org/en-US/docs/Learn_web_development/Getting_started/Web_standards/How_the_web_works" target="_blank" rel="noopener"&gt;MDN’s account of how the web works&lt;span class="sr-only"&gt;(opens in a new tab)&lt;/span&gt;&lt;/a&gt; lists DNS, the HTTP request, the server response, and the browser assembling the page as consecutive stages.&lt;/p&gt;</description></item><item><title>Caddy vs Nginx vs Nginx Proxy Manager</title><link>https://stackonward.com/posts/caddy-nginx-npm-comparison/</link><pubDate>Fri, 11 Sep 2026 05:15:00 +0800</pubDate><guid>https://stackonward.com/posts/caddy-nginx-npm-comparison/</guid><description>&lt;p&gt;Caddy vs Nginx is decided by where the reverse-proxy config lives and how certificates are renewed, not by a public request-per-second ranking. A small site with files in Git and default automatic HTTPS usually belongs on Caddy. Working Nginx configs, modules, or operator muscle stay on Nginx. A web UI for a handful of hosts is Nginx Proxy Manager, and that choice also means backing up an application, a database, and generated Nginx files.&lt;/p&gt;
&lt;figure&gt;
 &lt;img src="https://stackonward.com/images/posts/caddy-nginx-npm-comparison/en/proxy-decision-tree.svg" alt="Decision tree that chooses Caddy, Nginx, or Nginx Proxy Manager from file versus web UI maintenance, then from existing Nginx assets" loading="lazy" decoding="async"&gt;
 &lt;figcaption&gt;Existing Nginx assets stay on the file path. A GUI path still needs a backup of the admin data.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="caddy-vs-nginx-is-decided-by-the-config-source-of-truth"&gt;Caddy vs Nginx is decided by the config source of truth&lt;/h2&gt;
&lt;div class="table-responsive" role="region" aria-label="Scrollable table" tabindex="0"&gt;
 &lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Axis&lt;/th&gt;
					&lt;th&gt;Caddy&lt;/th&gt;
					&lt;th&gt;Nginx&lt;/th&gt;
					&lt;th&gt;Nginx Proxy Manager&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Daily config&lt;/td&gt;
					&lt;td&gt;Caddyfile or JSON&lt;/td&gt;
					&lt;td&gt;Nginx config files&lt;/td&gt;
					&lt;td&gt;Web admin UI&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;HTTPS&lt;/td&gt;
					&lt;td&gt;Certificate issue and renewal are default&lt;/td&gt;
					&lt;td&gt;Usually Certbot, acme.sh, or a cloud certificate&lt;/td&gt;
					&lt;td&gt;Common certificate actions in the UI&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Git&lt;/td&gt;
					&lt;td&gt;Reviewed as files&lt;/td&gt;
					&lt;td&gt;Reviewed as files&lt;/td&gt;
					&lt;td&gt;Main state lives in a database&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;WebSocket&lt;/td&gt;
					&lt;td&gt;HTTP reverse proxy handles the usual upgrade&lt;/td&gt;
					&lt;td&gt;Upgrade-related headers must be set correctly&lt;/td&gt;
					&lt;td&gt;Follows the generated Nginx config&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Cache and limits&lt;/td&gt;
					&lt;td&gt;Configurable&lt;/td&gt;
					&lt;td&gt;Mature and fine-grained&lt;/td&gt;
					&lt;td&gt;Bounded by the UI and Advanced config&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Extra state&lt;/td&gt;
					&lt;td&gt;Config plus certificate storage&lt;/td&gt;
					&lt;td&gt;Config, certificates, and related scripts&lt;/td&gt;
					&lt;td&gt;App, database, certificates, and generated config&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;Files are easy to review and roll back. A database is easy to click through, and a restore has to cover that database together with the certificates.&lt;/p&gt;</description></item><item><title>Docker Compose Production: Images, Health Checks, Secrets</title><link>https://stackonward.com/posts/docker-compose-production/</link><pubDate>Fri, 11 Sep 2026 05:12:00 +0800</pubDate><guid>https://stackonward.com/posts/docker-compose-production/</guid><description>&lt;p&gt;Docker Compose production on one host is a pinned image, durable data, a private app port, resource caps, and a stack that returns after a container or host restart. &lt;code&gt;docker compose up -d&lt;/code&gt; only reports that the containers started.&lt;/p&gt;
&lt;figure&gt;
 &lt;img src="https://stackonward.com/images/posts/docker-compose-production/en/compose-topology.svg" alt="Single-host Docker Compose production with a reverse proxy, app container, persistent volume, secret file, and size-capped logs" loading="lazy" decoding="async"&gt;
 &lt;figcaption&gt;The public internet should reach only the reverse proxy on 80/443. Keep the app port on loopback or on the container network.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="when-docker-compose-production-is-the-right-fit"&gt;When Docker Compose production is the right fit&lt;/h2&gt;
&lt;p&gt;Compose can run production when the whole stack lives on one machine, the service graph is small, and a host failure is recovered by a person or a script on another machine:&lt;/p&gt;</description></item><item><title>How to Configure Docker Volume Permissions and Secrets</title><link>https://stackonward.com/posts/docker-volume-permissions-secrets/</link><pubDate>Fri, 11 Sep 2026 05:12:00 +0800</pubDate><guid>https://stackonward.com/posts/docker-volume-permissions-secrets/</guid><description>&lt;p&gt;How to configure Docker volume permissions starts with a numeric UID and GID match, not a matching username. Put database files and other container-generated state on a named volume, bind-mount host-managed config read-only, and grant each secret only to the service that reads it as a file. Recreate the container to prove the mount. Restore a backup in a separate environment to prove the backup.&lt;/p&gt;
&lt;figure&gt;
 &lt;img src="https://stackonward.com/images/posts/docker-volume-permissions-secrets/en/storage-boundaries.svg" alt="An application container writes database state to a named volume, reads host config read-only, and reads a password from a granted secret file" loading="lazy" decoding="async"&gt;
 &lt;figcaption&gt;Named volumes hold writable state. Bind mounts hold host-managed config. Secret files are granted per service.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="choose-a-named-volume-bind-mount-or-tmpfs"&gt;Choose a named volume, bind mount, or tmpfs&lt;/h2&gt;
&lt;div class="table-responsive" role="region" aria-label="Scrollable table" tabindex="0"&gt;
 &lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Type&lt;/th&gt;
					&lt;th&gt;What belongs there&lt;/th&gt;
					&lt;th&gt;Why&lt;/th&gt;
					&lt;th&gt;Main risk&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;Named volume&lt;/td&gt;
					&lt;td&gt;Databases, queues, application state&lt;/td&gt;
					&lt;td&gt;Docker manages the lifecycle; the path is not tied to one host layout&lt;/td&gt;
					&lt;td&gt;Host backup and migration must name the volume and its driver&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Bind mount&lt;/td&gt;
					&lt;td&gt;Caddyfile, explicit data directories, host scripts&lt;/td&gt;
					&lt;td&gt;The path is visible to existing backup and permission tools&lt;/td&gt;
					&lt;td&gt;The host path, owner, and SELinux or AppArmor labels all have to match&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;tmpfs&lt;/td&gt;
					&lt;td&gt;Short-lived sensitive files or fast scratch data that must not persist&lt;/td&gt;
					&lt;td&gt;Nothing is written to durable disk&lt;/td&gt;
					&lt;td&gt;A restart drops the files; the size counts against container memory&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;&lt;a href="https://docs.docker.com/engine/storage/" target="_blank" rel="noopener"&gt;Docker storage&lt;span class="sr-only"&gt;(opens in a new tab)&lt;/span&gt;&lt;/a&gt; treats &lt;a href="https://docs.docker.com/engine/storage/volumes/" target="_blank" rel="noopener"&gt;volumes&lt;span class="sr-only"&gt;(opens in a new tab)&lt;/span&gt;&lt;/a&gt; as the default for data a container generates. &lt;a href="https://docs.docker.com/engine/storage/bind-mounts/" target="_blank" rel="noopener"&gt;Bind mounts&lt;span class="sr-only"&gt;(opens in a new tab)&lt;/span&gt;&lt;/a&gt; are for files the host already owns. &lt;a href="https://docs.docker.com/engine/storage/tmpfs/" target="_blank" rel="noopener"&gt;tmpfs&lt;span class="sr-only"&gt;(opens in a new tab)&lt;/span&gt;&lt;/a&gt; stays in host memory and is gone when the container stops. The image writable layer is the wrong place for runtime data: deleting the container deletes that layer.&lt;/p&gt;</description></item><item><title>How to Troubleshoot 502 and 504 Behind a Reverse Proxy</title><link>https://stackonward.com/posts/502-504-troubleshooting/</link><pubDate>Fri, 11 Sep 2026 05:08:00 +0800</pubDate><guid>https://stackonward.com/posts/502-504-troubleshooting/</guid><description>&lt;p&gt;How to troubleshoot 502 and 504 starts at the hop that produced the status. Both codes come from a gateway or reverse proxy: 502 Bad Gateway means that hop did not get a usable response from upstream; 504 Gateway Timeout means it waited past its limit. Keep the headers, then reach the upstream from that hop&amp;rsquo;s network.&lt;/p&gt;
&lt;figure&gt;
 &lt;img src="https://stackonward.com/images/posts/502-504-troubleshooting/en/gateway-failure-map.svg" alt="A client reaches an app through a CDN and reverse proxy; a failed connection becomes 502, a wait past the limit becomes 504" loading="lazy" decoding="async"&gt;
 &lt;figcaption&gt;Connection refused, DNS failure, and an invalid response usually point to 502. An upstream wait past the proxy deadline usually points to 504.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="how-to-troubleshoot-502-and-504-at-the-hop-that-returned-the-status"&gt;How to troubleshoot 502 and 504 at the hop that returned the status&lt;/h2&gt;
&lt;p&gt;A browser may sit behind Cloudflare or another CDN, a cloud load balancer, and Caddy or Nginx. Save the status, headers, time, and request ID:&lt;/p&gt;</description></item><item><title>Why Payment Methods Are Not Showing at Checkout</title><link>https://stackonward.com/posts/payment-method-availability-diagnosis/</link><pubDate>Fri, 11 Sep 2026 05:08:00 +0800</pubDate><guid>https://stackonward.com/posts/payment-method-availability-diagnosis/</guid><description>&lt;p&gt;RouteNest’s Pro monthly checkout produced three results. A Dutch buyer paying in EUR on desktop saw iDEAL and cards. A US buyer paying in USD saw cards plus Apple Pay or Google Pay. The same US buyer opening the page from a limited in-app WebView lost the wallet buttons. Why payment methods not showing at checkout for one customer, while another still sees them: each method has to pass the current account, this order, the buyer’s device, and this page’s settings. Change one of those, and the list can change.&lt;/p&gt;</description></item><item><title>Automatic HTTPS Certificate Renewal with Caddy and ACME</title><link>https://stackonward.com/posts/https-certificate-automatic-renewal/</link><pubDate>Fri, 11 Sep 2026 05:05:00 +0800</pubDate><guid>https://stackonward.com/posts/https-certificate-automatic-renewal/</guid><description>&lt;p&gt;Automatic HTTPS certificate renewal in Caddy is not a cron job. Caddy obtains a certificate, serves HTTPS, redirects HTTP, and renews in the background when the hostname, public DNS, ACME challenge path, and certificate storage all work. A running Caddy process does not prove the next ACME attempt will succeed.&lt;/p&gt;
&lt;figure&gt;
 &lt;img src="https://stackonward.com/images/posts/https-certificate-automatic-renewal/en/acme-renewal-flow.svg" alt="Flow from Caddy reading a hostname, completing ACME validation, storing the certificate, renewing in the background, and checking TLS from outside" loading="lazy" decoding="async"&gt;
 &lt;figcaption&gt;After issuance, the certificate, private key, and ACME account state are written to the data directory. Background renewal uses the same challenge path.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="automatic-https-certificate-renewal-only-starts-when-these-conditions-hold"&gt;Automatic HTTPS certificate renewal only starts when these conditions hold&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://caddyserver.com/docs/automatic-https" target="_blank" rel="noopener"&gt;Caddy&amp;rsquo;s Automatic HTTPS documentation&lt;span class="sr-only"&gt;(opens in a new tab)&lt;/span&gt;&lt;/a&gt; requires all of the following at once:&lt;/p&gt;</description></item><item><title>Automatic rollback for a failed deployment: transaction state, traffic switch, and rollback limits</title><link>https://stackonward.com/posts/automatic-deployment-recovery/</link><pubDate>Fri, 11 Sep 2026 05:00:00 +0800</pubDate><guid>https://stackonward.com/posts/automatic-deployment-recovery/</guid><description>&lt;p&gt;Automatic rollback for a failed deployment is not &lt;code&gt;docker compose down&lt;/code&gt; after the script dies. Recovery depends on three facts: whether the candidate already took public traffic, whether the previous upstream can still be restored, and whether business checks finished. If the previous version is still serving, stop only this candidate. If the traffic switch may be half-done, restore the previous upstream. If the new version is still in the observation window, switch back. Recovery should finish the commit records only when the release has already entered the commit phase.&lt;/p&gt;</description></item><item><title>Docker Compose Blue-Green: Health Check, Switch, Drain</title><link>https://stackonward.com/posts/single-server-short-downtime-deployment/</link><pubDate>Fri, 11 Sep 2026 05:00:00 +0800</pubDate><guid>https://stackonward.com/posts/single-server-short-downtime-deployment/</guid><description>&lt;p&gt;A Docker Compose blue-green deployment on one server starts the candidate, waits until the app is ready, validates and switches the Caddy upstream, proves the public hostname, drains old connections, then stops the old version. Do not delete the old container before the candidate is taking traffic.&lt;/p&gt;
&lt;p&gt;That sequence can shrink the unavailable window. The host, disk, Docker daemon, and shared database are still single points of failure, so measure the window instead of calling the result zero downtime.&lt;/p&gt;
&lt;figure&gt;
 &lt;img src="https://stackonward.com/images/posts/single-server-short-downtime-deployment/en/blue-green-release-flow.svg" alt="Old version keeps traffic on 18080; candidate starts on 18081 and passes a loopback health check; Caddy switches, the public hostname is verified, then old connections drain" loading="lazy" decoding="async"&gt;
 &lt;figcaption&gt;The gateway switch is the only public cutover. Candidate startup and warmup happen before that switch.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="docker-compose-blue-green-only-works-under-these-constraints"&gt;Docker Compose blue-green only works under these constraints&lt;/h2&gt;
&lt;p&gt;Run two app instances on one host only when the service meets all of these conditions:&lt;/p&gt;</description></item><item><title>Hosted Checkout vs Embedded Checkout</title><link>https://stackonward.com/posts/hosted-embedded-custom-checkout-comparison/</link><pubDate>Fri, 11 Sep 2026 05:00:00 +0800</pubDate><guid>https://stackonward.com/posts/hosted-embedded-custom-checkout-comparison/</guid><description>&lt;p&gt;RouteNest put the Pro payment form on the pricing page. A Dutch buyer chose iDEAL, and the browser still left for the bank. After the buyer authorized and came back, membership did not turn on, because the payment webhook had not arrived. Nothing was broken: where the form lives, whether the payment leaves the page, and whether payment is confirmed are three different jobs.&lt;/p&gt;
&lt;p&gt;If the provider&amp;rsquo;s full checkout page already covers the required payment methods and fields, start with hosted checkout. Keep a hosted embedded form only when the product configuration before payment cannot be abandoned. Split into hosted components when a prebuilt form cannot hold seats, add-ons, or a live quote. Generating the card number field in your own page jumps the security and compliance work; that is usually the wrong starting point for SaaS.&lt;/p&gt;</description></item><item><title>Merchant of Record vs Payment Processor: Who Sells the Order</title><link>https://stackonward.com/posts/payment-processor-vs-merchant-of-record/</link><pubDate>Fri, 11 Sep 2026 05:00:00 +0800</pubDate><guid>https://stackonward.com/posts/payment-processor-vs-merchant-of-record/</guid><description>&lt;p&gt;A customer buys a pretax $100 SaaS annual subscription and pays $20 more in tax. Both setups can mark the order &lt;code&gt;paid&lt;/code&gt;. Direct payment processing still has your company selling to the customer and carrying tax, refunds, and delivery. A merchant of record (MoR) becomes the seller the buyer faces on in-scope orders, then pays you a supplier amount after tax, fees, and contract adjustments.&lt;/p&gt;
&lt;p&gt;That is the merchant of record vs payment processor difference that matters. A payment processor answers how authorization, capture, and settlement complete. An MoR also changes who sold the product to the buyer. Hosted versus embedded checkout, whose API you call, and which balance first held the funds cannot prove seller identity on their own.&lt;/p&gt;</description></item><item><title>Why Pin a Docker Image Digest: Reproducible Deploys and Rollbacks</title><link>https://stackonward.com/posts/docker-image-digest-pinning/</link><pubDate>Fri, 11 Sep 2026 05:00:00 +0800</pubDate><guid>https://stackonward.com/posts/docker-image-digest-pinning/</guid><description>&lt;p&gt;Pin a Docker image digest by writing the production image as &lt;code&gt;repository:tag@sha256:digest&lt;/code&gt;. The version tag shows which release you planned. The digest is the content Docker must pull. &lt;code&gt;latest&lt;/code&gt;, &lt;code&gt;stable&lt;/code&gt;, or a movable version tag cannot guarantee that a rebuild or rollback returns the original image.&lt;/p&gt;
&lt;figure&gt;
 &lt;img src="https://stackonward.com/images/posts/docker-image-digest-pinning/en/image-identity-chain.svg" alt="A movable tag points to an OCI index digest, which selects amd64 or arm64 platform manifests, and the running container records the actual image" loading="lazy" decoding="async"&gt;
 &lt;figcaption&gt;Pinning the index digest freezes the platform set. Each node still pulls the manifest that matches its architecture.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="why-pin-a-docker-image-digest-instead-of-a-moving-tag"&gt;Why pin a Docker image digest instead of a moving tag&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://docs.docker.com/dhi/explore/security-concepts/digests/" target="_blank" rel="noopener"&gt;Docker&amp;rsquo;s image digest documentation&lt;span class="sr-only"&gt;(opens in a new tab)&lt;/span&gt;&lt;/a&gt; defines a digest as a SHA-256 identifier of image content. A registry maintainer can retarget the same tag to a patched image. The original digest does not move with the tag.&lt;/p&gt;</description></item><item><title>How to Use Claude Code Hooks: Deterministic Checks</title><link>https://stackonward.com/posts/claude-code-codex-hooks/</link><pubDate>Fri, 11 Sep 2026 04:48:00 +0800</pubDate><guid>https://stackonward.com/posts/claude-code-codex-hooks/</guid><description>&lt;p&gt;How to use Claude Code hooks is not about memorizing every event. It is about whether the action has a clear trigger and a unique result. A project config check belongs in a hook: after the file is written, run the same command. Valid config exits success. Invalid config returns a determined error. The model does not choose the standard.&lt;/p&gt;
&lt;p&gt;Project rules name the config path and the fixed command. The prompt describes this change. A skill handles live judgment. The hook only calls the project command on a matching event. Claude Code and Codex keep their own event config and share the same check.&lt;/p&gt;</description></item><item><title>How to Write Claude Code Skills: Repeatable Workflows</title><link>https://stackonward.com/posts/agent-skill-project-workflow/</link><pubDate>Fri, 11 Sep 2026 04:40:00 +0800</pubDate><guid>https://stackonward.com/posts/agent-skill-project-workflow/</guid><description>&lt;p&gt;How to write Claude Code skills depends on whether the flow repeats across tasks, whether it still needs on-the-spot judgment, and whether the project can observe a result after it runs. Facts that stay true in a repository belong in &lt;a href="https://stackonward.com/posts/agents-md-claude-md-project-rules/"&gt;project rules&lt;/a&gt;. A one-off requirement belongs in the &lt;a href="https://stackonward.com/posts/coding-agent-task-prompt/"&gt;current task prompt&lt;/a&gt;. Actions that must run on an event belong in a hook. External services and data come from MCP or other tools.&lt;/p&gt;
&lt;p&gt;A full-stack project that often changes an existing API contract has to find the real producer, confirm approved field meanings, sync direct consumers, run existing checks, and observe a page or a call. The fields and the range change every time. The judgment order stays stable. That flow belongs in a project skill. The field names, paths, and acceptance result for this run still come from the current prompt.&lt;/p&gt;</description></item><item><title>OneDev Tutorial: Docker Compose, Git, and CI/CD on One Server</title><link>https://stackonward.com/posts/onedev-production-deployment/</link><pubDate>Fri, 11 Sep 2026 04:24:00 +0800</pubDate><guid>https://stackonward.com/posts/onedev-production-deployment/</guid><description>&lt;p&gt;A OneDev tutorial starts with one instance that can host Git, review, issues, CI/CD, artifacts, and packages. Keep code on a server you operate, and skip running a separate Git service, CI, and package registry, when that trade-off is worth the ops work. The official single-node baseline is also lower than full GitLab.&lt;/p&gt;
&lt;p&gt;The environment is one 64-bit Linux host: OneDev and PostgreSQL run under Docker Compose, and Caddy terminates HTTPS. After the instance is ready, create a Go project and prove push, tests, image build, production deploy, backup, restore, and upgrade. A container in &lt;code&gt;running&lt;/code&gt; is not enough. The repository, Git SSH, the pipeline, and restore still have to work.&lt;/p&gt;</description></item><item><title>Migrate a Website to a New Domain: 301, 308, Canonical</title><link>https://stackonward.com/posts/website-domain-migration/</link><pubDate>Fri, 11 Sep 2026 04:22:00 +0800</pubDate><guid>https://stackonward.com/posts/website-domain-migration/</guid><description>&lt;p&gt;When you migrate a website to a new domain, each live old URL should take one permanent hop to the matching new URL, including the path and any query string that still belongs to the page. Canonical tags, internal links, sitemaps, structured data, and analytics on the new host must already name the new domain.&lt;/p&gt;
&lt;figure&gt;
 &lt;img src="https://stackonward.com/images/posts/website-domain-migration/en/domain-migration-map.svg" alt="Old-host home, article, topic, and asset URLs each taking one permanent redirect to the same path on the new host" loading="lazy" decoding="async"&gt;
 &lt;figcaption&gt;Home, article, topic, and asset each keep their path on the new host.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="build-a-one-to-one-url-map-before-the-cutover"&gt;Build a one-to-one URL map before the cutover&lt;/h2&gt;
&lt;p&gt;Export old URLs from the sitemap, access logs, Search Console, backlinks, and the database. Then give each address one destination:&lt;/p&gt;</description></item><item><title>Cache-Control for Static Assets: ETag and Fingerprints</title><link>https://stackonward.com/posts/static-asset-cache-control/</link><pubDate>Fri, 11 Sep 2026 04:20:00 +0800</pubDate><guid>https://stackonward.com/posts/static-asset-cache-control/</guid><description>&lt;p&gt;Cache-Control for static assets cannot be one header copied onto every path. Fingerprinted CSS, JavaScript, and fonts can stay cached for a year. HTML has to revalidate so it can pick up new URLs. Images that keep the same filename can stay cached only for as long as outdated bytes are acceptable. Responses that carry account or payment data must not enter a shared cache.&lt;/p&gt;
&lt;figure&gt;
 &lt;img src="https://stackonward.com/images/posts/static-asset-cache-control/en/cache-policy-map.svg" alt="Decision map from public versus sensitive responses to no-store, one-year immutable cache, or short cache and revalidation" loading="lazy" decoding="async"&gt;
 &lt;figcaption&gt;Cache lifetime follows whether the URL changes with the content, whether the response is public, and how long stale copies are allowed to remain.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="cache-control-for-static-assets-starts-from-whether-the-url-changes"&gt;Cache-Control for static assets starts from whether the URL changes&lt;/h2&gt;
&lt;div class="table-responsive" role="region" aria-label="Scrollable table" tabindex="0"&gt;
 &lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Resource&lt;/th&gt;
					&lt;th&gt;When the URL changes&lt;/th&gt;
					&lt;th&gt;Usual policy&lt;/th&gt;
					&lt;th&gt;Publish requirement&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;HTML&lt;/td&gt;
					&lt;td&gt;The address stays; the document changes&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;no-cache&lt;/code&gt; or a short &lt;code&gt;max-age&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Revalidate or expire in time for the next release&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Fingerprinted CSS, JS, and fonts&lt;/td&gt;
					&lt;td&gt;The filename changes with the bytes&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;public, max-age=31536000, immutable&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;HTML must reference the new URL&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Unversioned images and PDFs&lt;/td&gt;
					&lt;td&gt;The same address may be overwritten&lt;/td&gt;
					&lt;td&gt;Short cache plus ETag&lt;/td&gt;
					&lt;td&gt;Purge that URL or change the URL after a replace&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Public API responses&lt;/td&gt;
					&lt;td&gt;Business semantics decide&lt;/td&gt;
					&lt;td&gt;Explicit &lt;code&gt;public&lt;/code&gt; or &lt;code&gt;private&lt;/code&gt;, &lt;code&gt;s-maxage&lt;/code&gt;, and &lt;code&gt;Vary&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;The cache key must cover every response difference&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Sensitive or one-time responses&lt;/td&gt;
					&lt;td&gt;Reuse is not allowed&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;no-store&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;The CDN must not cache them either&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;&lt;code&gt;app.4f81c9a2.js&lt;/code&gt; can be treated as immutable only because a content change produces a new filename. A directory named &lt;code&gt;/assets/&lt;/code&gt; does not prove that every file inside it is immutable.&lt;/p&gt;</description></item><item><title>How to Model SaaS Products, Plans, and Prices</title><link>https://stackonward.com/posts/product-plan-price-saas-catalog-modeling/</link><pubDate>Fri, 11 Sep 2026 04:18:00 +0800</pubDate><guid>https://stackonward.com/posts/product-plan-price-saas-catalog-modeling/</guid><description>&lt;p&gt;RouteNest is about to raise Pro monthly from $19 to $24. If the system stores one mutable row—&lt;code&gt;plan=pro, price=19, stripe_price_id=...&lt;/code&gt;—writing 24 over 19 removes the basis for existing subscriptions. Whether the $190 annual price moves, whether the euro price is recomputed, which amount &lt;code&gt;WELCOME20&lt;/code&gt; discounts, and which Paddle or Creem object to charge all become guesses.&lt;/p&gt;
&lt;p&gt;How to model SaaS products and prices depends on when each fact changes. Product says what is sold. Plan says which features and limits the buyer receives. Price says amount, currency, interval, and version. A promotion only changes a qualifying sale. A provider mapping only projects an internal Price onto a payment channel. Orders and subscriptions must store the snapshot from the sale; they cannot reread &amp;ldquo;the current price&amp;rdquo; to explain the past.&lt;/p&gt;</description></item><item><title>Why a Payment Processor Application Gets Rejected, and How to Resubmit Evidence</title><link>https://stackonward.com/posts/payment-provider-review-rejected-verifiable-materials/</link><pubDate>Fri, 11 Sep 2026 04:16:00 +0800</pubDate><guid>https://stackonward.com/posts/payment-provider-review-rejected-verifiable-materials/</guid><description>&lt;p&gt;A payment processor application is rejected when a reviewer cannot independently match who is selling, through which domain, what the buyer receives after payment, who handles cancellation or refunds, and who receives the net proceeds. Genuine files still fail when those facts point at different businesses.&lt;/p&gt;
&lt;p&gt;The application names &lt;code&gt;Northstar Labs, Inc.&lt;/code&gt;. The site footer only shows the product name &lt;code&gt;RouteNest&lt;/code&gt;. The pricing page sells a USD 19 monthly auto-renewing subscription, while the refund page still describes a one-time download. The product is behind login and the reviewer has no account. The payout account still names the founder personally. Each of those statements can be true. Together they do not say who sells what, how it is delivered, or where the money goes.&lt;/p&gt;</description></item><item><title>How to Expose Docker Ports: 127.0.0.1, 0.0.0.0, Firewalls</title><link>https://stackonward.com/posts/docker-port-firewall-loopback/</link><pubDate>Fri, 11 Sep 2026 04:14:00 +0800</pubDate><guid>https://stackonward.com/posts/docker-port-firewall-loopback/</guid><description>&lt;p&gt;How to expose Docker ports depends on the process listen address inside the container, the host publish bind, and the firewall or cloud security group in front of the host. Listening on &lt;code&gt;0.0.0.0&lt;/code&gt; inside a container does not put that port on the public internet.&lt;/p&gt;
&lt;figure&gt;
 &lt;img src="https://stackonward.com/images/posts/docker-port-firewall-loopback/en/network-boundaries.svg" alt="Public traffic crosses a cloud security group and host firewall to a reverse proxy; the app and database talk only on the Docker network" loading="lazy" decoding="async"&gt;
 &lt;figcaption&gt;The app and database stay on the Docker network. The public internet reaches only the ports the proxy publishes.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="how-to-expose-docker-ports-at-each-network-layer"&gt;How to expose Docker ports at each network layer&lt;/h2&gt;
&lt;div class="table-responsive" role="region" aria-label="Scrollable table" tabindex="0"&gt;
 &lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Where it is set&lt;/th&gt;
					&lt;th&gt;Example&lt;/th&gt;
					&lt;th&gt;What it means&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;App process listen&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;0.0.0.0:8080&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Accept 8080 on every interface in this container&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;App process listen&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;127.0.0.1:8080&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Accept 8080 only on this container’s loopback&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Docker port publish&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;127.0.0.1:8080:8080&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Host-local processes can reach container 8080 on host 8080&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Docker port publish&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;0.0.0.0:8080:8080&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;Every IPv4 address on the host publishes 8080; the public internet may reach it&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;Compose internal access&lt;/td&gt;
					&lt;td&gt;&lt;code&gt;http://app:8080&lt;/code&gt;&lt;/td&gt;
					&lt;td&gt;A peer on the same network uses the service name and the container port&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;The two uses of &lt;code&gt;0.0.0.0&lt;/code&gt; are not the same action. Listening on every interface inside the container lets a proxy on the same Docker network connect. Publishing the port onto every host interface widens the host entry.&lt;/p&gt;</description></item><item><title>DNS Record Types: A, AAAA, CNAME, TXT, and CAA</title><link>https://stackonward.com/posts/dns-record-types/</link><pubDate>Fri, 11 Sep 2026 04:12:00 +0800</pubDate><guid>https://stackonward.com/posts/dns-record-types/</guid><description>&lt;p&gt;DNS record types A, AAAA, CNAME, TXT, and CAA store different answers. A and AAAA hold the server address. CNAME holds another name so a CDN or host can keep the later addresses. TXT holds ownership proofs and mail policy. CAA names which certificate authorities may issue.&lt;/p&gt;
&lt;p&gt;When the hostname should reach a server you control, publish A or AAAA. When the platform issues a hostname instead of a stable IP, publish CNAME. TXT and CAA do not send traffic; they only constrain verification and issuance.&lt;/p&gt;
&lt;figure&gt;
 &lt;img src="https://stackonward.com/images/posts/dns-record-types/en/dns-record-map.svg" alt="How DNS record types A, AAAA, CNAME, TXT, and CAA split between traffic routing and stored policy" loading="lazy" decoding="async"&gt;
 &lt;figcaption&gt;A, AAAA, and CNAME decide where traffic goes. TXT and CAA store verification, policy, or certificate limits.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="how-dns-record-types-a-aaaa-cname-txt-and-caa-differ"&gt;How DNS record types A, AAAA, CNAME, TXT, and CAA differ&lt;/h2&gt;
&lt;div class="table-responsive" role="region" aria-label="Scrollable table" tabindex="0"&gt;
 &lt;table&gt;
	&lt;thead&gt;
			&lt;tr&gt;
					&lt;th&gt;Record&lt;/th&gt;
					&lt;th&gt;Value&lt;/th&gt;
					&lt;th&gt;Common job&lt;/th&gt;
					&lt;th&gt;Usual failure&lt;/th&gt;
			&lt;/tr&gt;
	&lt;/thead&gt;
	&lt;tbody&gt;
			&lt;tr&gt;
					&lt;td&gt;A&lt;/td&gt;
					&lt;td&gt;IPv4 address&lt;/td&gt;
					&lt;td&gt;Point a name at an IPv4 server&lt;/td&gt;
					&lt;td&gt;The server IP changed and the record did not&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;AAAA&lt;/td&gt;
					&lt;td&gt;IPv6 address&lt;/td&gt;
					&lt;td&gt;Point a name at an IPv6 server&lt;/td&gt;
					&lt;td&gt;IPv6-capable networks try an address the service cannot serve&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;CNAME&lt;/td&gt;
					&lt;td&gt;Another name&lt;/td&gt;
					&lt;td&gt;CDN, hosted platform, object storage&lt;/td&gt;
					&lt;td&gt;The same owner also has A, TXT, or MX&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;TXT&lt;/td&gt;
					&lt;td&gt;A text string&lt;/td&gt;
					&lt;td&gt;Domain ownership, SPF, DKIM, DMARC&lt;/td&gt;
					&lt;td&gt;The console appended the zone, or the copied value gained a space&lt;/td&gt;
			&lt;/tr&gt;
			&lt;tr&gt;
					&lt;td&gt;CAA&lt;/td&gt;
					&lt;td&gt;A CA identifier&lt;/td&gt;
					&lt;td&gt;Limit which issuers may mint certificates&lt;/td&gt;
					&lt;td&gt;The CA already in use is missing, so renewal fails&lt;/td&gt;
			&lt;/tr&gt;
	&lt;/tbody&gt;
&lt;/table&gt;

&lt;/div&gt;

&lt;p&gt;Consoles label the DNS name as Host, Name, or Record name. Each record still has a name, a type, a value, and a TTL. &lt;a href="https://docs.cloud.google.com/dns/docs/records-overview" target="_blank" rel="noopener"&gt;Google Cloud DNS records overview&lt;span class="sr-only"&gt;(opens in a new tab)&lt;/span&gt;&lt;/a&gt; lists common uses. A and CNAME semantics are in &lt;a href="https://www.rfc-editor.org/rfc/rfc1034" target="_blank" rel="noopener"&gt;RFC 1034&lt;span class="sr-only"&gt;(opens in a new tab)&lt;/span&gt;&lt;/a&gt;. AAAA is &lt;a href="https://www.rfc-editor.org/rfc/rfc3596" target="_blank" rel="noopener"&gt;RFC 3596&lt;span class="sr-only"&gt;(opens in a new tab)&lt;/span&gt;&lt;/a&gt;. CAA is &lt;a href="https://www.rfc-editor.org/rfc/rfc8659" target="_blank" rel="noopener"&gt;RFC 8659&lt;span class="sr-only"&gt;(opens in a new tab)&lt;/span&gt;&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>How to Write Claude Code Prompts: Current Task Only</title><link>https://stackonward.com/posts/coding-agent-task-prompt/</link><pubDate>Fri, 11 Sep 2026 04:10:00 +0800</pubDate><guid>https://stackonward.com/posts/coding-agent-task-prompt/</guid><description>&lt;p&gt;How to write Claude Code prompts depends on what this task still lacks after project configuration. Project rules already hold the repo map, commands, and long-lived boundaries. Skills already hold repeatable workflows. Hooks and CI run deterministic actions. Permission policy limits tools. The current prompt only needs the state to change, the evidence you already have, the affected range, the authorization boundary, and the result that must appear when the work is done.&lt;/p&gt;
&lt;p&gt;The prompt does not need YAML, XML, or a fixed form. A local defect can be short. Context that crosses several owners is added only when that context changes the result. The test is not word count or field count. It is whether the agent can start from the current project, make the change inside authorization, and confirm the software reached the target state.&lt;/p&gt;</description></item><item><title>When to Run Database Migrations: Expand, Migrate, Contract</title><link>https://stackonward.com/posts/database-migration-release-order/</link><pubDate>Fri, 11 Sep 2026 04:10:00 +0800</pubDate><guid>https://stackonward.com/posts/database-migration-release-order/</guid><description>&lt;p&gt;When to run database migrations depends on whether the current schema can serve both the old and the new application. Add compatible structure first, ship the new app, then backfill data. Drop columns, finish renames, and tighten constraints in a later release after the old version has left.&lt;/p&gt;
&lt;figure&gt;
 &lt;img src="https://stackonward.com/images/posts/database-migration-release-order/en/expand-migrate-contract.svg" alt="Expand adds compatible structure, Migrate deploys compatible apps and backfills in batches, Contract drops old structure after old consumers exit" loading="lazy" decoding="async"&gt;
 &lt;figcaption&gt;Destructive DDL is not tied to the first rollout of new code.&lt;/figcaption&gt;
&lt;/figure&gt;

&lt;h2 id="when-to-run-database-migrations-depends-on-compatibility"&gt;When to run database migrations depends on compatibility&lt;/h2&gt;
&lt;p&gt;The same DDL can wait on a lock for seconds or for minutes depending on table size, PostgreSQL major version, and live writes. Confirm those facts, plus how old and new code touch the table, before choosing a stage.&lt;/p&gt;</description></item><item><title>How to Write AGENTS.md: Root vs Directory Rules</title><link>https://stackonward.com/posts/agents-md-claude-md-project-rules/</link><pubDate>Fri, 11 Sep 2026 03:20:00 +0800</pubDate><guid>https://stackonward.com/posts/agents-md-claude-md-project-rules/</guid><description>&lt;p&gt;How to write AGENTS.md starts with who a fact stays true for, and whether it should change a coding agent&amp;rsquo;s next file or command. Repo-wide maps, canonical commands, and cross-module boundaries belong at the repository root. Rules that only bind backend, frontend, or a worker sit in that directory. Habits that hold across repositories stay in the user-global file. What this task must change stays in the current prompt. CLAUDE.md uses the same content boundary; Claude Code just finds it through its own files.&lt;/p&gt;
&lt;p&gt;If the root file also stores frontend commands, backend migrations, a release workflow, and “do not touch deploy this time,” four different lifetimes enter unrelated sessions. Confirm the fact in code or build config first, then keep the long-lived rule in the smallest scope that still needs it. Workflows, task variables, and hard stops belong to other mechanisms. The same scope-and-mechanism split is already mapped in &lt;a href="https://stackonward.com/posts/claude-code-codex-configuration-guide/"&gt;Claude Code configuration&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Claude Code Configuration: Global vs Project</title><link>https://stackonward.com/posts/claude-code-codex-configuration-guide/</link><pubDate>Fri, 11 Sep 2026 02:00:00 +0800</pubDate><guid>https://stackonward.com/posts/claude-code-codex-configuration-guide/</guid><description>&lt;p&gt;Claude Code configuration has to answer two questions: who should see this fact, and how it should take effect. Habits that hold across repositories belong at the user layer. Facts the team shares belong in the repo. Rules that only bind frontend or backend sit next to that directory. This change stays in the prompt. A repeatable judgment workflow becomes a skill. A mechanical action that must run is a hook, a permission, or existing CI.&lt;/p&gt;
&lt;p&gt;The split does not require a separate config repository or a new directory template. It starts from the repo already in use and moves facts out of chat logs, global files, and duplicated project rules until each fact has one source of truth. Claude Code and Codex can share that source. Each client still owns its file format, discovery order, and events.&lt;/p&gt;</description></item><item><title>About StackOnward</title><link>https://stackonward.com/about/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://stackonward.com/about/</guid><description>&lt;p&gt;StackOnward follows a software product from idea through development, launch, and operations.&lt;/p&gt;
&lt;h2 id="focus"&gt;Focus&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Product and engineering&lt;/strong&gt;: product design, architecture, frontend, backend, data, and AI.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Payments and subscriptions&lt;/strong&gt;: pricing, checkout, payments, subscriptions, entitlements, and finance.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Websites and growth&lt;/strong&gt;: site delivery, performance, SEO, content, social, ads, and sales.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Operations and automation&lt;/strong&gt;: servers, containers, monitoring, releases, workflows, and scripts.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="browse"&gt;Browse&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href="https://stackonward.com/posts/"&gt;Articles&lt;/a&gt;: all articles by date.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackonward.com/topics/"&gt;Topics&lt;/a&gt;: browse by domain.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackonward.com/series/"&gt;Series&lt;/a&gt;: read in order.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stackonward.com/resources/"&gt;Resources&lt;/a&gt;: scripts, templates, workflows, and examples.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="contact"&gt;Contact&lt;/h2&gt;
&lt;p&gt;Technical questions, editorial collaboration, and project consulting:&lt;/p&gt;
&lt;p&gt;&lt;span class="contact-links"&gt;&lt;a href="https://github.com/stackonward" target="_blank" rel="noopener"&gt;GitHub&lt;span class="sr-only"&gt;(opens in a new tab)&lt;/span&gt;&lt;/a&gt; · &lt;a href="mailto:yhy20010203@gmail.com"&gt;E-Mail&lt;/a&gt; · &lt;span class="wechat-contact wechat-contact-content" data-wechat-contact&gt;
 &lt;button class="wechat-contact-trigger" type="button" aria-expanded="false" aria-controls="wechat-contact-about" aria-label="Show WeChat QR code"&gt;&lt;span&gt;WeChat&lt;/span&gt;
 &lt;/button&gt;
 &lt;span class="wechat-contact-panel" id="wechat-contact-about" role="dialog" aria-label="WeChat contact" aria-hidden="true"&gt;
 &lt;span class="wechat-contact-header"&gt;
 &lt;span class="wechat-contact-details"&gt;
 &lt;strong&gt;Add on WeChat&lt;/strong&gt;
 &lt;button class="copy-inline wechat-contact-copy" type="button" aria-label="Copy WeChat ID: a4759821" title="Copy WeChat ID" data-copy-control data-copy-value="a4759821" data-copy-label="WeChat ID"&gt;
 &lt;span class="copy-inline-text"&gt;a4759821&lt;/span&gt;
 &lt;span class="copy-inline-action" aria-hidden="true"&gt;
 &lt;span class="copy-control-icon copy-control-icon-default"&gt;&lt;svg viewBox="0 0 24 24" aria-hidden="true"&gt;&lt;rect x="8" y="8" width="11" height="11" rx="2"&gt;&lt;/rect&gt;&lt;path d="M16 8V6a2 2 0 0 0-2-2H6a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h2"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;
 &lt;span class="copy-control-icon copy-control-icon-success"&gt;&lt;svg viewBox="0 0 24 24" aria-hidden="true"&gt;&lt;path d="m5 12 4 4L19 6"&gt;&lt;/path&gt;&lt;/svg&gt;&lt;/span&gt;
 &lt;/span&gt;
 &lt;span class="sr-only" aria-live="polite" data-copy-feedback&gt;&lt;/span&gt;
&lt;/button&gt;

 &lt;/span&gt;
 &lt;img src="https://stackonward.com/brand/character-companion-v1.webp" width="800" height="800" alt="" loading="lazy" decoding="async"&gt;
 &lt;/span&gt;
 &lt;span class="wechat-contact-qr"&gt;
 &lt;img src="https://stackonward.com/brand/wechat-qr.png" width="680" height="680" alt="WeChat QR code for a4759821" loading="lazy" decoding="async"&gt;
 &lt;/span&gt;
 &lt;/span&gt;
&lt;/span&gt;
 · &lt;a href="https://x.com/yhy319139806" target="_blank" rel="noopener"&gt;X&lt;span class="sr-only"&gt;(opens in a new tab)&lt;/span&gt;&lt;/a&gt; · &lt;a href="https://stackonward.com/index.xml"&gt;RSS&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description></item></channel></rss>