All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Scope: this file tracks the repository and shared assets — tooling, AI skills, build scripts, and Pragmatica Core version bumps. Each book now versions independently with its own reader-facing changelog:
book/CHANGELOG.md(JBCT) andbook-pfd/CHANGELOG.md(PFD). SeeBOOK-VERSIONING.md. Pre-existing entries below that describe book content are kept as history.
One derived Pragmatica version declaration, and a pin check that can actually fail (ai-tools/pragmatica-version.json, ai-tools/pragmatica-pins.py, ai-tools/check-drift.sh, website/build.js) — the constant at check-drift.sh:14 was compared against grep -rn -E '1\.0\.0-rc[0-9]+' skills agents ../book/*.md | grep -v -F "$V", blind four ways, each demonstrated by a positive control before it was replaced (#60). It compared the declaration against nothing external, so both sides could be stale together; it searched three paths, so website/, README.md, training/pom.xml, examples/, proposals/ and articles/ were never looked at; its pattern matched only -rcN, so a pin mutated to 1.0.1 inside its own space left it green; and grep -v filters by line, so a line carrying both the pinned version and 1.0.0-rc9 was dropped on the pinned match. The replacement declares the pin once, compares every surface against it, and compares the declaration itself against Maven Central’s <release> — that last axis has since moved to a scheduled workflow and is no longer part of a PR check; see Changed, below. The space is git ls-files minus dated records, and the run prints what it skipped (600 tracked, 397 scanned, 160 excluded by glob, 3 symlink, 40 undecodable) rather than asserting its own coverage. Attribution carries the artifact id, because org.pragmatica-lite:jbct-maven-plugin is at 0.4.6 on its own release line and comparing it to core’s pin would demand a wrong edit forever.
No version value was moved, and the disagreements are named rather than hidden. The repository disagrees with itself and which value is right is an editorial claim, so 22 dated, individually justified inventory entries carry them — printed in full and counted on every run, never silent. They classify as 2 mechanical pins awaiting a ruling, 2 editorial claims a human must re-assert, 15 historical statements that must not move, 2 belonging to another artifact and 1 not Pragmatica at all. Issue #60 names three historical lines; there are fifteen, including four (Pragmatica Core 0.9.0+) availability floors and a Performance as of v0.15.1 sentence binding a benchmark to the release it was measured on. The check distinguishes them because it cannot infer them: each entry is anchored to a line substring and an occurrence count, so an exception that stops describing its file fails instead of rotting green, and every deferral records the pin it was written against and expires the moment the pin moves. Also found, and outside every previous check’s space: examples/pom.xml:15 pins core at 0.9.0 while examples/README.md in the same directory claims 1.0.0-rc1.
A staleness tripwire relating each course lesson blurb to its book chapter (ai-tools/blurb-stamps.py, ai-tools/check-drift.sh) — the 47 blurbs under website/course/{jbct,pfd,architecture-synthesis}/ shadow book chapters and no checker read any of them, so #70 took book/comparison.md from 470 lines to 218 while website/course/jbct/comparison.md went on describing three deleted sections, with every existing check green (#69). Each blurb now carries reviewed-at: <chapter blob id> in front matter; check-drift.sh step 7 fails when a chapter’s bytes move without the blurb being re-asserted, and blurb-stamps.py --update <blurb> is how an author asserts it still holds. The join is filename equality, verified both directions across all 47. The count line (examined 47 blurb(s) against 3 book(s)) prints on every run, and an examined-nothing run fails rather than exiting green.
Only the failure direction is load-bearing. --update clears the tripwire without reading anything and the check cannot tell the difference, so a current stamp is an unfalsified assertion, not a verified blurb — including the 47 seeded ones, which reviewed nothing. Comparing blurb to chapter by meaning is not available; the mechanism is deliberately weak and should stay weak.
Eight more book-owned sync blocks, and a block can now have several destinations (ai-tools/sync-book-blocks.py) — four-return-shapes, return-type-matrix, promise-boundary, jbct-approach, typed-errors, wrapped-terminal-causes, bare-cause, module-promotion, joining the seven that existed. dest accepts a list, so a block the skill and the jbct-coder agent both carry is declared once and written to both; the typed-error and return-type blocks go to skills/jbct/SKILL.md, skills/jbct/fundamentals/four-return-kinds.md and agents/jbct-coder.md from one extraction. Verified by mutation: an edit inside a synced block makes --check exit 1, and a renamed book heading makes it exit 2.
Thirteenth article on the site: “Software’s Second Free Lunch” (website/build.js, website/content/articles.md, articles/softwares-second-free-lunch.md) — renders at /articles/softwares-second-free-lunch/, listed first under The method; site goes from 87 to 88 pages. Published on dev.to since 2026-07-22 but never added to the site’s article system, so its canonical_url (now added to the frontmatter, per the pattern the canonical-URL cleanup established) had nowhere to point. Page text synced to the retrieved published version — the only drift was the closing pragmatica.dev mention being a link in the published copy. The dev.to copy still declares no canonical; repointing it at the site edition is a separate dev.to-side action.
Pirsch analytics on pragmatica.dev (website/templates/*.html, website/next-step/index.html) — one <script defer> immediately before </head> in the six files that carry one, reaching all 87 built pages. Cookieless, so no consent banner is required. The point is outbound-click tracking: Leanpub gives authors no referrer or source data at all, and the site sends Referrer-Policy: strict-origin-when-cross-origin, so Leanpub sees only https://pragmatica.dev and never which book page sent the visitor. Outbound clicks are tracked by default with no extra attributes.
Maintenance note (superseded by the proxy repoint under Changed — the enumeration grep and the data-code sentences below no longer hold): there is no shared head partial and build.js does no head injection, so the snippet exists as six independent copies — centralising it would cost more build machinery than a one-line script tag is worth. grep -rn pirsch website/ enumerates all six in one command. Run it after any change to the identification code or the script URL: a stale code in one of six templates makes a single page type silently stop recording while the other five keep working, and the dashboard shows plausible data throughout. The identification code is domain-specific — pragmaticalabs.io has a different one and the two must not be crossed.
The JBCT worksheet has a web edition (website/build.js) — book/appendix-d-worksheet.md renders at /java/jbct/worksheet/ and is linked from the JBCT course footer, mirroring the PFD and Architecture Synthesis worksheet routes. The slug is registered as a non-lesson so it does not enter the course spine. Site goes from 85 to 86 pages.
The articles are published on the site, and the canonical URLs finally resolve (website/build.js, website/content/articles.md, articles/) — twelve cross-posted articles now render at /articles/<slug>/ behind an index at /articles/, taking the site from 72 to 85 pages. This closes a defect that had been live for months and was invisible to every check: ten published articles carried a canonical_url pointing at a page that did not exist, and one canonicalised to the site’s homepage — returning 200, so nothing ever flagged it, while telling search engines that article was the front page. PUBLISHING_SCHEDULE.md tracks twelve articles; dev.to has thirty published, which is why eight of the broken ones were invisible. Page text is the retrieved published version rather than the local draft, since the two had drifted; a 301 carries article #1’s prefix-less legacy canonical to its new home.
Two more book-owned sync blocks (ai-tools/sync-book-blocks.py) — predicate-naming and recovery-triple, joining zone-verbs. Both were written into the book first and pulled into the skill by the sync, rather than hand-copied, because every drift incident this repo has had traced to a hand copy.
The recovery triple reaches the tooling (ai-tools/skills/jbct/SKILL.md, agents/jbct-coder.md, agents/jbct-reviewer.md) — the book has carried ## The Recovery Triple: BER, FER, Design-Out with the instruction “name the triple for each step that changes state”, and ai-tools/ contained zero occurrences of it: not BER, FER, design-out, or compensate. .recover( appeared only as an API example. An agent writing JBCT code had no prompt to state a guarantee for a dropped failure, and the reviewer had no rule to catch its absence. The reviewer gains ### Recovery Checks (return-kind violation, absorption without justification, untested absorbed path); the coder gains a self-validation item shaped like the inverse-pair rule — a stated justification is a complete answer; silence is not — with a guard against inventing retries or outboxes to avoid writing the sentence.
A fifth corpus sheet: the Shopify pod-era derivation (website/next-step/corpus/shopify.toml) — transcribed from the pre-registered blind run whose every prediction graded HIT. It turns sharded from a unit-tested rule into a reproduced derivation: BLIND-DERIVATION-SO.md:73 names Shopify as “the sharded value the other runs never touched”, and this is now the corpus entry for it. The sheet reproduces the recorded vector on every axis and carries three refusals no other sheet can exercise — 1000+ developers pressing nothing on topology (F21 at the top of its range, against Discord’s four engineers at the bottom), 11 TB/s of read volume stopping at the replicas rung because the read model has not diverged, and a contention row at a scope that also carries a partition key, which must not shard because more sharding cannot help a conflict inside one tenant. Building it surfaced a book-level scope error, below.
The sheet’s normal form is enforced, and divergence is computed (website/next-step/) — Card 5’s one-row-per-unit rule was specified in the book and honoured by convention only. diverges and diverges_on are now rejected by the entry gate as UNNORMALIZED: a sheet that asserts a divergence has done the deriving itself, and the engine was reading a conclusion and calling it a demand. Divergence is computed instead — q7 by comparing each unit’s cadence against the system-scoped baseline, q9 by comparing typed attributes against it, with two storage shapes on one unit pressing polyglot and a unit differing on two or more attributes pressing per-component. All four corpus sheets reproduce their recorded derivations with the assertions deleted, which is the evidence the divergence was always computable from what they already stated. Comparing typed fields also retires the exact-match vocabulary that could not distinguish Companies House’s “shape” from profile 3’s “data shape” — a distinction that had already caused one misclassification. New: silence is no longer reported as absence, so a sheet where no unit states a cadence reports divergence as unknowable.
Press rules for sharded and streaming, each two-condition and each with its refusals tested: sharded needs write volume past one node and a natural partition key (volume without a key is a capacity problem, not an axis move; a partition key never rescues a contention row, which “buys hardware and keeps the melt”); streaming needs the volume that earns a partitioned log and a replay-from-position need, at data-class scope. Coverage moves from 7 of 11 pressable ledger values to 9 of 11.
verify — the exit gate, Card 6’s five arithmetic rules (website/next-step/verify.js) — latency decomposition down the critical path (sequential adds, parallel costs its max), tail composition in series and under fan-out, envelope composition by correlation, availability multiplication with an earned-independence check, and the mechanism bill against Q8’s operator count. 25 tests. The refusals are the load-bearing part and are pinned: a floor the sheet did not supply is UNVERIFIED, never a default; parallel availability arithmetic is refused outright when deploys, certs, regions or config are shared, and no number is produced; a sheet carrying no verification inputs reports not attempted rather than reading as passing. Wired as step 5 of derive.
next_step entry gate and the mechanical half of the derivation, as a browser playground (website/next-step/, served at /method/architecture-synthesis/next-step/) — the book’s mechanizability claim shipped as software. Dependency-free ES modules parse the answer-sheet TOML subset and run: the entry gate’s five disciplines (priced · scoped · decomposed · triaged · surfaced), the null vector, pruning by explicit mandate strike, pressing each axis against the ledger’s provides / via / costs entries, resolving the pressed vector at the scope the pressure was recorded at, and recovery per effectful operation derived from domain-shape facts (design-out checked first, then inverse, then decay). 83 tests, including tests that pin the refusals — a recovery tie is emitted as a judgment point and never decided, no axis moves without a ledger entry justifying it, and prune never infers a strike from prose. Runs entirely client-side.
Counterexample issue templates for PFD and JBCT (.github/ISSUE_TEMPLATE/) — the counterexamples page invited reports against all three methods while only Architecture Synthesis had a channel. A PFD report argues about a decomposition and its change drivers; a JBCT report about a rule that makes code worse. Neither has a registered-prediction corpus, so the page says reports there are read and answered rather than graded.
ai-tools/sync-book-blocks.py — makes the books the source for enumerable rules the tooling duplicates (import ordering, member ordering, zone verb vocabulary, test naming). Each block names a book section by heading; the script extracts it into the destination between <!-- book:<id> --> markers, so those regions are build output rather than hand-copies. --check fails CI when a destination is stale (exit 1) or when a book heading was renamed or markers went missing (exit 2) — a renamed heading fails loudly instead of quietly leaving a stale copy.
ai-tools/check-drift.sh — drift guard for the AI tooling, wired into CI as .github/workflows/checks.yml. Skills are installed away from this repo (~/.claude/skills), so a stale reference here becomes an invisible wrong answer there. Checks: references to retired documents, links that do not resolve, links that escape their skill directory (these cannot survive installation — book citations must be pragmatica.dev URLs), Pragmatica Core pins that disagree with the declared canonical version, book version headers that disagree with the book’s own CHANGELOG.md, and divergence between this repo and an installed copy. The install-drift check skips automatically where no ~/.claude exists, so CI runs the rest.
Orphan detection in the site build — verifyLinks() checked that links resolve, not that pages are reachable, so a page nothing linked to passed silently. It now fails the build, with an explicit allowlist for legacy root pages kept only so previously-published URLs do not 404.
.github/workflows/upstream-pin.yml, ai-tools/pragmatica-pins.py, ai-tools/check-drift.sh, CLAUDE.md) — of the axes in pragmatica-pins.py, exactly one reaches the network: the comparison of the declared pin against Maven Central’s <release>. Inside a blocking check that axis had only two available behaviours when Central was unreachable, and both were wrong. Failing would block merges on someone else’s outage; not failing — what it did — printed declaration-vs-upstream axis NOT checked and exited 0 inside an otherwise-green run, which is a gate reporting success while examining nothing. Measured before the change: with urlopen raising, --check printed that line and returned exit code 0. The axis now runs under a new --upstream flag from a daily scheduled workflow (17 6 * * *, plus workflow_dispatch), where unreachable and malformed are failures, not skips — a scheduled job blocks no pull request, so the strictness costs a notification instead of a merge. --offline is gone with it; it existed only to suppress a probe --check no longer makes.
The pointer is verified, not asserted. A comment saying where an axis went is exactly the kind of claim that rots green after the axis is deleted, so --check reads the workflow file on every run and fails if it is missing or has stopped invoking --upstream — probed by hiding the file, which turned --check red. What did not change is what the axis compares: a known_behind exception still passes, because it is anchored to one exact upstream value and stops matching the day Central moves. Both failure paths were exercised against the real code, not simulated: an unreachable host (FAIL: UNREACHABLE … axis NOT checked, exit 1) and a decayed anchor against live Central (FAIL: upstream Maven Central publishes 1.0.0-rc3, this repository declares 1.0.0-rc1, exit 1). The hermetic axes are untouched and still bite: <version> mutated to 1.0.0-rc9 in ai-tools/skills/jbct/SKILL.md moved the run to 1 findings and exit 1, and the occurrence counts are otherwise unmoved at 181 total — 38 agree, 27 excepted, 0 findings, 0 need classification, 116 out of scope.website/templates/*.html, website/next-step/index.html, website/build.js) — https://api.pirsch.io/pa.js replaced with /p/pl.js plus explicit data-hit-endpoint="/p/pv", data-event-endpoint="/p/e", data-session-endpoint="/p/s", served by the Cloudflare Worker on pragmatica.dev/p/* (worker source and rotation: oss/internal/pirsch-proxy/). The driver is measurement bias, not cosmetics: content blockers that blocklist api.pirsch.io silently drop hits, and blocker rates differ by channel, so the loss skewed exactly the per-channel comparison the install exists for; first-party paths on our own domain match no blocklist. data-code is dropped deliberately — dashboard identity now comes from a per-domain access key held as a Worker secret, selected by the tracked page’s hostname, so the tag is domain-neutral and the old cross-domain code hazard no longer exists. id="pianjs" must stay exactly: the script locates its own tag by it. build.js’s link checker gains a /p/ exemption — the path exists only at the edge and resolves on no page in dist/.
Maintenance note (current — replaces the install entry’s): the snippet is still six independent copies and the stale-copy hazard is unchanged, but the new tag carries no “pirsch” string, so grep -rn pirsch website/ no longer finds it. grep -rn 'id="pianjs"' website/ is the enumeration that works; run it after any change to the script URL or endpoints. Verified after the swap: 6 source copies, 87 of 87 built pages, zero api.pirsch.io remnants, live /p/pl.js serving the script.ai-tools/skills/jbct/SKILL.md, ai-tools/skills/aether-coder/SKILL.md, ai-tools/agents/jbct-reviewer.md) — the jbct skill claimed 37 rules, aether-coder claimed 41, and the CLI ships 69 rule classes. A count in a derived artifact tracking another repository’s source rots by construction, so the number is gone from all three rather than updated to a value that would be wrong again at the next release. The jbct skill now names the rule-ID families instead (JBCT-RET-*, JBCT-VO-*, JBCT-EX-*, JBCT-NAM-*, JBCT-LAM-*, JBCT-STY-*, JBCT-LOG-*, JBCT-MIX-*), which are stable and are what every finding is reported under. The CLI has no rule-listing subcommand to point readers at.rules.js) — load was accepted per path only, while sharded reads write volume past one node’s ceiling and streaming reads the one data class whose volume earns a partitioned log, both per data class. A sheet built to the stated rule could not express the fact those values turn on, which is how the Shopify sheet found it. data-class is now an accepted Q5 scope, and the book was corrected to match (AS 1.1.2).separated follows the rule the derivations actually apply — read-model divergence plus volume, with staleness as a cost rather than a third condition. The book, the working ledger and the two graded blind runs carried three incompatible statements of this rule while the engine implemented a fourth (divergence alone). LEDGER.md, axes-and-ledger.md:45, Card 2b and ledger.js now agree, and the engine gained the volume condition it lacked plus a cost check that refuses the move where the path is contracted strict.unified runtime and serverless move from “needs a rule” to UNFORCED: no corpus run, worked example or blind derivation has produced their forcing pressure, so a rule would not implement the method but invent a demand and then obey it. That is a different gap from a missing rule, and the coverage test now records which kind it is.read_shape renamed to read_model across the engine, the four corpus sheets and the spec — Architecture Synthesis 1.1.0 named the fact read-model divergence and began eliciting it in Card 1’s second row, so the field now spells what the book asks for. The old name predated the book having a word for it, and collided with Card 3’s demand shapes — the collision the book fix removed. Reverting a single field name breaks four golden assertions, so the wiring is load-bearing rather than decorative.press.js’s header no longer claims read-model divergence is unelicited — Card 1 asks for it as of AS 1.1.0. This is the third stale “does not exist yet” comment found this cycle, after the ledger claim in these notes and derive.js’s blocked-press header; each outlived the thing it said was missing. The header now records what the engine genuinely still does not do: diverges is an assertion the sheet makes, not a comparison computed across per-unit rows, so Card 5’s one-row-per-unit normal form is specified in the book and unenforced in software.website/next-step/coverage.test.js). Unreached: unified runtime, serverless, streaming, sharded — each needs a press rule and a sheet field to carry the fact the rule would read (coupling and topology uncertainty, duty cycle, per-data-class volume, a natural partition key). The test fails if a ledger value gains a pressedBy and nobody decides whether a rule reaches it, so the gap cannot widen silently.separated is under-implemented. book-arch-meta/LEDGER.md:121 requires three conditions together — a read path with its own tight SLO and its own scale shape and tolerable staleness. The rule reads shape divergence alone; it reproduced two corpus runs on the strength of those sheets rather than of the rule.axes-and-ledger.md:39 forward-references the per-value provides / mechanism / costs entries to the reference cards, and Card 2 carries only axes and values. The content exists — book-arch-meta/LEDGER.md v0.2, which ledger.js is transcribed from — so this is a publishing gap, not authorial work.skills/jbct/project-structure/organization.md) — JBCT-ORD-01 and the import-order rule were in the book (book/project-structure.md) but had never reached the skill. Now generated from it.skills/jbct/SKILL.md) — the skill pointed at the book for the full tables and carried a divergent verb list of its own; the tables are now generated from book/basic-patterns.md.The JBCT skill carried the pre-4.9.0 typed-error idiom (ai-tools/skills/jbct/SKILL.md, fundamentals/four-return-kinds.md) — its error example showed a data-carrying record with a hand-written message() and a Throwable component, the shape the book replaced on 2026-08-27 with the record-plus-FACTORY idiom and Cause.Wrapped; the agent had been updated and the skill had not, so the two disagreed and nothing could see it. The skill also still opened the return-type rule with “exactly one of four kinds”, the sentence the book restated as four semantic shapes on 2026-09-05, and had no text at all for Option<List<T>> as a forbidden nesting, the Promise process-boundary rule, the normalization boundary, module promotion, or construction-that-scales. All of it is now generated from the book by the sync blocks above; the jbct-coder header’s last-modified date moves to 2026-09-06. Its Pragmatica Core pin stays at the declared canonical (#60 owns that). Found while answering a question about which JBCT pointer to give an external contributor on pragmatica#898.
The telescope rule reaches the skill, and the sync learned to rewrite book links (ai-tools/sync-book-blocks.py,
ai-tools/skills/jbct/project-structure/organization.md) — the JBCT skill carried no statement of the telescope
rule at all, while sync-book-blocks.py pulled import ordering and member ordering from the same book chapter and
left The Telescope Rule: How Structure Grows unsynced. Found by the regeneration run, where an isolated builder
produced a correct flat placement without ever being given the rule. The block is now book-owned and synced (83
lines). Around it, the skill states when the rule fires: placement present in the input makes the package path
derived, not chosen — applied in the build phase before the first file, verified in the verification phase as an
explicit step that names which level came from which fact. Recorded there too is why this is a skill obligation and
not a lint rule: the package path is the only record in the codebase of which use cases cohere under which change
driver, so a checker has one side of the comparison and not two, and no rule among the 77 attempts it.
The sync now rewrites book-relative chapter links to their published URLs, since a skill installed under
~/.claude/skills/ can reach no relative path into the book — the first synced block containing such a link
exposed the gap, and check-drift.sh already enforced the same rule on hand-written skill text.
BOOK-VERSIONING.md says what a defect correction costs — MAJOR’s “content readers relied on being
removed or replaced” could be read to cover any corrected prescription, which is how the 2026-09-05 review
corrections were first misclassified as two paired majors. The clause now states that correcting a defect is
a PATCH even when the defect was a prescription, and cites PFD’s own precedent: the locking → guarded-field
correction replaced a prescription readers had followed and shipped as 2.4.2. Owner ruling, 2026-09-05.
A published article claimed an upgrade capability the runtime does not provide, and named commands that no
longer exist (articles/aether-let-java-be-java.md, articles/jbct-aether-series/00-aether-let-java-be-java.md)
— Pragmatica Aether: Let Java Be Java, live on the site and dev.to since 2026-02-13, printed four
aether update commands and promised “instant rollback with one command” with traffic that “immediately shifts
back”. The commands were real when the article was published; pragmatica’s changelog records aether update
being replaced by aether deploy --canary and then removed, and the command appears nowhere in aether/docs,
so only the article carried it. The passage now uses the aether deploy forms, replaces the instant-rollback
sentences with the mechanism the Aether maintainer verified at source (per-node weighted selection over the
endpoints each node currently knows, not atomic across the cluster and not instantaneous), states as unverified
whether traffic then divides in the requested proportion, and cites pragmatica#291 for the rolling-update API
not existing server-side. A dated note records what changed underneath the section, because nothing was wrong
when written. Verifiers: the Aether maintainer for the mechanism and the ticket; this session for the changelog
removal notice, the DeployCommand surface, and the docs being clean. The dev.to copy (3254469) is corrected by
PUT; the Medium copy exists and can only be edited by hand.
The site’s evidence claim now matches the book’s own grading (website/templates/front-door.html,
website/content/architecture-synthesis.md) — both said four architectures were “derived blind”, while
Architecture Synthesis grades those same four runs itself: one executed by operators quarantined from the
outcome, two registered in advance by an operator who was not, and one drawn from memory of the same public
sources it would be graded against. The book was more careful than the pages selling it. Both now state the
protocol and the unequal insulation. Reported by a review of the site handed to this session, 2026-09-05.
/method/ stopped saying the free courses are planned (website/content/method.md) — both course
editions have been live and linked from the front door for weeks; the hub page still told readers to buy the
book “until each one is built”. It now links them. Same review; also recorded in the roadmap-conference
inventory of 2026-09-04.
Article corrections: dead draft canonicals and an unearned availability claim (articles/*.md, articles/fail-safe-legacy.md, articles/jbct-aether-series/04-monolith-deploys.md) — eight unpublished drafts (errors-as-values, four-return-types, parse-dont-validate, result-option-pattern, value-objects-cookbook, legacy-adoption-strategy, single-level-of-abstraction, patterns-as-business-vocabulary) carried canonical_url: https://pragmatica.dev/<slug>, a root path that returns 404; repointed to /articles/<slug> so a future cross-post cannot canonicalise to a dead page (the page itself still has to exist first, per the pipeline order). Fail-Safe Your Legacy Java in One Sprint (live on the site and dev.to since 2026-01-27) stated that Aether “provides exactly this guarantee” and that “the same request, processed by any available node, produc[es] the same result”; rewritten to name the mechanism and its bound — a cluster of at least three nodes advancing shared state by majority consensus, requests routed to live nodes, capacity lost below half, progress on shared state stopped at half or more, and “same answer from any node” holding only for reads that go through the consensus path — in both the site source and the series copy, and pushed to the dev.to copy by PUT. The dev.to copy of Software’s Second Free Lunch (article 4206323) declared its canonical as .../softwares-second-free-lunch) with a trailing parenthesis; corrected by PUT. Found during the 2026-09-04 roadmap-conference inventory.
The book contradicted itself on test naming — book/basic-patterns.md still taught the retired three-segment methodName_outcome_condition while systematic-application.md and chapter-summaries.md taught the 4.3.1 method_[scenario_]expectation. The 4.3.1 change had missed this section, which is where the skill’s copy of the old rule came from.
Bidirectional tooling drift — the installed copies of jbct-coder, jbct-reviewer and code-reviewer had accumulated content that was never backported (the *State naming rule, the @NullReturn intent annotation, the Verify.Is/built-in-VO catalog rules, the mirrored-API sibling-diff review pass, and code-reviewer’s rescoping to non-JBCT code), while the repo copies were ahead on the pattern-origin framing. Both directions reconciled against the book.
BPMN framing — jbct-coder and jbct-reviewer claimed the six patterns “map to” BPMN constructs. The book is explicit that the correspondence is “corroboration, not foundation — the catalog is derived from the process, not from the notation” (book/basic-patterns.md:18). Patterns now lead from the process side, with BPMN retained as a dictionary.
Test-naming rule — skill still carried the pre-4.3.1 three-segment form; relaxed to method_[scenario_]expectation (two or more segments) per JBCT-NAM-05.
Dead book references — CODING_GUIDE.md (retired 2026-06-22) and the non-existent book/ch09-thread-safety.md / book/ch07-basic-patterns.md paths replaced with published web-edition URLs.
Stale book version headers — book/introduction.md and book/from-process-to-patterns.md announced JBCT v4.3.0 against a 4.3.1 changelog.
mapWith / flatMapWith / ensureWith combinator family collapses each stage to one lambda-free line. Includes the gating-vs-evidence rule: ensureWith is where parse-don’t-validate runs out (transient outcomes only), while a load-bearing check must return evidence and accrete it. Wired into both build scripts and the TOC.email, money) allow static imports without collision, which is the reason shared names like of/create/valueOf are rejected.✅/❌/⚠ marks in the Ch. 3 (return-types) and Ch. 6 (error-handling) comparison tables are emoji codepoints absent from DejaVu, so xelatex rendered them as blank boxes. Added a monochrome Noto Emoji fallback (mapped via active-character definitions in metadata.yaml, stray variation-selectors stripped) so they render; the font dependency is checked in build-pdf.sh.ch02-design-methodology was missing from both build scripts). Added that chapter to the build, and reconciled every body title and cross-reference to the canonical TOC numbering (1–20, with 9b and 15a/15b as lettered sub-chapters). Removed the unused, stale book/manuscript/ mirror.appendix-b TransferFunds exercises now use the canonical factories Money.money(...) and TransferFunds.transferFunds(...) (were Money.of / TransferFunds.create); ch11 timeout test uses UseCase.useCase(...) (was UseCase.create).Promise.await() forbidden in business logic — zero-tolerance rule; @TerminalOperation for legitimate casesResult/Promise values — zero-tolerance rule; expression-based code enforcement@Contract and @TerminalOperation annotations — replace @SuppressWarnings for exemptionsPromise.await() forbidden in business logic — new zero-tolerance rule; use @TerminalOperation for legitimate cases (CLI, fire-and-forget)Result/Promise values — new zero-tolerance rule; every value must be returned or handled@Contract and @TerminalOperation annotations — dedicated annotations replace @SuppressWarnings for void return and await exemptionssiy/pragmatica-lite to pragmaticalabs/pragmaticavoid return type is the right choice: API conformance and fire-and-forget side effectsVerify.Is::present to API reference listings (combined not-null + not-blank for CharSequence)CreateUserRequest example showing Result.all() compositionVerify.Is::notNull to Verify.Is::present across all docs, examples, and AI toolsVoid type parameter (forbidden) vs void return type (fire-and-forget) across guide, series, book, and AI toolsEmail, Url, Uuid, NonBlankString, IsoDateTime)articles/six-patterns-that-cover-everything.mdarticles/underlying-process.md (full article)articles/linkedin-data-transformation.md (LinkedIn post)/jbct-review skill for thorough parallel code reviewVerify.ensure() syntax across documentation
Verify.ensure(CAUSE, value, predicate) (deprecated, forRemoval)Verify.ensure(value, predicate, CAUSE) (cause-at-end)Verify.ensureOption() for elegant Result<Option<T>> validation patternVerify.ensureOption() pattern
Result.sequence() for collecting results from iterables.getOrThrow() for Result and Optionall() for Option and Promise (was Result-only)unused recordVerify.ensureFn() removed - use .filter(cause, predicate) insteadResult.tryOf() aliases, Result.onOk()/onErr()/run() aliasesall() methods for for-comprehension styleensureFn occurrences across 35 files to filter patternjbct check after code generationjbct check before manual reviewcause.result() over Result.failure(cause)
all()/Fn/Tuple support from 9 to 15 parametersenum General for fixed-message errors)Causes.forValue() to Causes.forOneValue() (deprecated name fix).match() to .fold() in controller example (correct API)Causes.forValue() to Causes.forOneValue() (deprecated name fix)Causes.forValue() with Causes.forOneValue() across all documentation{} placeholders to %s (String.format syntax) in all Causes factory examplescalculateTotal() method to follow Single Level of Abstraction principleUserLoginError to LoginError to match actual interface definitionsEmailExists enum constant with consistent naming (EMAIL_EXISTS)Major release: Thread Safety, Concurrency, and Series v2.0.0 (100% Parity)
Foundational Concepts: Immutability and Thread Confinement
Promise Resolution and Thread Safety
Fork-Join: Independence and Thread Safety unified section
Thread Safety Quick Reference table (before Testing Strategy)
Thread Safety notes for each pattern
Testing: Mutable test state is acceptable
Series v2.0.0: 100% Parity with CODING_GUIDE.md v2.0.0 (+1,439 lines across all parts)
Option.option(nullable) wrapping pattern.or(), .orElse(), .recover() for fallback valuesValid prefix)HttpClient not HTTPClient)Enhanced documentation for easier adoption
README: 30-Second Pitch with before/after code comparison
README: Quick Wins section for incremental adoption
Part 2: Spring to JBCT Translation table
Part 2: “Why Not Java Standard Library?” comparison
Part 2: Migration strategy for existing codebases
Part 2: Real-world validation examples
Part 2: “When Exceptions Are Still OK” section
Part 2: Basic testing moved forward from Part 5
Part 2: Pragmatica Core Quick Reference
Part 3: “Why This Rule Exists” section for Single Pattern Per Function
Part 1: Evaluation Framework worked example
Progressive “Smart Wrapper” → “monad” terminology across series
Part 3: Simplified async iteration example
Part 1: Added “Try It Now” exercises
Terminology clarifications
CODING_GUIDE.md: Major comprehensive improvements (+1074 lines)
// private Email {} // Not yet supported in JavaImpact: CODING_GUIDE.md evolved from 3076 lines to 4150 lines of comprehensive, reference-quality documentation that bridges theoretical concepts with practical implementation guidance.
Option.from(Optional), Result.unitResult(), Promise.unitPromise()Promise.async(Runnable), use Promise.lift(ThrowingRunnable) insteadPromise.failAll(), Promise.cancelAll().apply(), .fold(), .withSuccess(), .withFailure(), .withResult().resolve(), .succeed(), .fail(), .cancel(), .isResolved(), .timeout(), .mapResult(), .replaceResult(), .trace().isPresent(), .isEmpty(), .isSuccess(), .isFailure(), .unwrap(), .expect(), .stream(), .toOptional()Verify.Is::minLength to Verify.Is::lenBetween in Part 6ValidatedUser occurrences to ValidUser in Part 6Option.option(nullable) at adapter entry, .orElse(null) at adapter exit (DB writes only)Void type usageResult<Unit> or Promise<Unit> for operations without meaningful return valuesResult.unitResult() for successful Result<Unit> creationValidated prefix with Valid prefix throughout all documentation
ValidatedUser → ValidUserValidatedCredentials → ValidCredentialsValidatedRequest → ValidRequest (where applicable)Valid is concise and conveys the same meaning; Validated adds no semantic valueflatMap2(), mapToUnit()onPresentRun(), onEmptyRun(), onSuccessRun(), onFailureRun(), onResultRun()onSuccessAsync(), onSuccessRunAsync(), etc.filter(Cause, Promise<Boolean>)promise(TimeSpan delay, ...)::new, inline only with captured parametersHttpClient not HTTPClient)